/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef itkImageFileReader_hxx #define itkImageFileReader_hxx #include "itkObjectFactory.h" #include "itkImageIOFactory.h" #include "itkConvertPixelBuffer.h" #include "itkPixelTraits.h" #include "itkVectorImage.h" #include "itkMetaDataObject.h" #include "itksys/SystemTools.hxx" #include "itkMakeUniqueForOverwrite.h" #include namespace itk { template ImageFileReader::ImageFileReader() { m_ImageIO = nullptr; this->SetFileName(""); m_UserSpecifiedImageIO = false; m_UseStreaming = true; } template void ImageFileReader::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); itkPrintSelfObjectMacro(ImageIO); os << indent << "UserSpecifiedImageIO: " << (m_UserSpecifiedImageIO ? "On" : "Off") << std::endl; os << indent << "UseStreaming: " << (m_UseStreaming ? "On" : "Off") << std::endl; os << indent << "ExceptionMessage: " << m_ExceptionMessage << std::endl; os << indent << "ActualIORegion: " << m_ActualIORegion << std::endl; } template void ImageFileReader::SetImageIO(ImageIOBase * imageIO) { itkDebugMacro("setting ImageIO to " << imageIO); if (this->m_ImageIO != imageIO) { this->m_ImageIO = imageIO; this->Modified(); } m_UserSpecifiedImageIO = true; } template void ImageFileReader::GenerateOutputInformation() { typename TOutputImage::Pointer output = this->GetOutput(); itkDebugMacro("Reading file for GenerateOutputInformation()" << this->GetFileName()); // Check to see if we can read the file given the name or prefix // if (this->GetFileName().empty()) { throw ImageFileReaderException(__FILE__, __LINE__, "FileName must be specified", ITK_LOCATION); } // Test if the file exists and if it can be opened. // An exception will be thrown otherwise. // We catch the exception because some ImageIO's may not actually // open a file. Still reports file error if no ImageIO is loaded. try { m_ExceptionMessage = ""; this->TestFileExistanceAndReadability(); } catch (const itk::ExceptionObject & err) { m_ExceptionMessage = err.GetDescription(); } if (m_UserSpecifiedImageIO == false) // try creating via factory { m_ImageIO = ImageIOFactory::CreateImageIO(this->GetFileName().c_str(), ImageIOFactory::IOFileModeEnum::ReadMode); } if (m_ImageIO.IsNull()) { std::ostringstream msg; msg << " Could not create IO object for reading file " << this->GetFileName().c_str() << std::endl; if (!m_ExceptionMessage.empty()) { msg << m_ExceptionMessage; } else { std::list allobjects = ObjectFactoryBase::CreateAllInstance("itkImageIOBase"); if (!allobjects.empty()) { msg << " Tried to create one of the following:" << std::endl; for (auto & allobject : allobjects) { auto * io = dynamic_cast(allobject.GetPointer()); msg << " " << io->GetNameOfClass() << std::endl; } msg << " You probably failed to set a file suffix, or" << std::endl << " set the suffix to an unsupported type." << std::endl; } else { msg << " There are no registered IO factories." << std::endl << " Please visit https://www.itk.org/Wiki/ITK/FAQ#NoFactoryException to diagnose the problem." << std::endl; } } ImageFileReaderException e(__FILE__, __LINE__, msg.str().c_str(), ITK_LOCATION); throw e; } // Got to allocate space for the image. Determine the characteristics of // the image. // m_ImageIO->SetFileName(this->GetFileName().c_str()); m_ImageIO->ReadImageInformation(); SizeType dimSize; double spacing[TOutputImage::ImageDimension]; double origin[TOutputImage::ImageDimension]; typename TOutputImage::DirectionType direction; std::vector> directionIO; const unsigned int numberOfDimensionsIO = m_ImageIO->GetNumberOfDimensions(); if (numberOfDimensionsIO > TOutputImage::ImageDimension) { for (unsigned int k = 0; k < numberOfDimensionsIO; ++k) { directionIO.push_back(m_ImageIO->GetDefaultDirection(k)); } } else { for (unsigned int k = 0; k < numberOfDimensionsIO; ++k) { directionIO.push_back(m_ImageIO->GetDirection(k)); } } std::vector axis; for (unsigned int i = 0; i < TOutputImage::ImageDimension; ++i) { if (i < numberOfDimensionsIO) { dimSize[i] = m_ImageIO->GetDimensions(i); spacing[i] = m_ImageIO->GetSpacing(i); origin[i] = m_ImageIO->GetOrigin(i); // Please note: direction cosines are stored as columns of the // direction matrix axis = directionIO[i]; for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j) { if (j < numberOfDimensionsIO) { direction[j][i] = axis[j]; } else { direction[j][i] = 0.0; } } } else { // Number of dimensions in the output is more than number of dimensions // in the ImageIO object (the file). Use default values for the size, // spacing, origin and direction for the final (degenerate) dimensions. dimSize[i] = 1; spacing[i] = 1.0; origin[i] = 0.0; for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j) { if (i == j) { direction[j][i] = 1.0; } else { direction[j][i] = 0.0; } } } } MetaDataDictionary & thisDic = m_ImageIO->GetMetaDataDictionary(); // Store original directions and spacing EncapsulateMetaData>( thisDic, "ITK_original_spacing", std::vector(spacing, spacing + TOutputImage::ImageDimension)); EncapsulateMetaData(thisDic, "ITK_original_direction", direction); // Spacing is expected to be greater than 0 // If negative, flip image direction along this axis. // and store this information in the metadata for (unsigned int i = 0; i < TOutputImage::ImageDimension; ++i) { if (spacing[i] < 0) { spacing[i] = -spacing[i]; for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j) { direction[j][i] = -direction[j][i]; } } } output->SetSpacing(spacing); // Set the image spacing output->SetOrigin(origin); // Set the image origin output->SetDirection(direction); // Set the image direction cosines // Copy MetaDataDictionary from instantiated reader to output image. output->SetMetaDataDictionary(thisDic); this->SetMetaDataDictionary(thisDic); const ImageRegionType region(dimSize); // If a VectorImage, this requires us to set the // VectorLength before allocate if (strcmp(output->GetNameOfClass(), "VectorImage") == 0) { using AccessorFunctorType = typename TOutputImage::AccessorFunctorType; AccessorFunctorType::SetVectorLength(output, m_ImageIO->GetNumberOfComponents()); } output->SetLargestPossibleRegion(region); } template void ImageFileReader::TestFileExistanceAndReadability() { // Test if the file exists. if (!itksys::SystemTools::FileExists(this->GetFileName().c_str())) { ImageFileReaderException e(__FILE__, __LINE__); std::ostringstream msg; msg << "The file doesn't exist. " << std::endl << "Filename = " << this->GetFileName() << std::endl; e.SetDescription(msg.str().c_str()); throw e; } // Test if the file can be open for reading access. std::ifstream readTester; #ifdef _MSC_VER const std::wstring uncpath = itksys::SystemTools::ConvertToWindowsExtendedPath(this->GetFileName().c_str()); readTester.open(uncpath.c_str(), std::ios::binary); #else readTester.open(this->GetFileName().c_str()); #endif if (readTester.fail()) { readTester.close(); std::ostringstream msg; msg << "The file couldn't be opened for reading. " << std::endl << "Filename: " << this->GetFileName() << std::endl; ImageFileReaderException e(__FILE__, __LINE__, msg.str().c_str(), ITK_LOCATION); throw e; } readTester.close(); } template void ImageFileReader::EnlargeOutputRequestedRegion(DataObject * output) { itkDebugMacro("Starting EnlargeOutputRequestedRegion() "); typename TOutputImage::Pointer out = dynamic_cast(output); typename TOutputImage::RegionType largestRegion = out->GetLargestPossibleRegion(); ImageRegionType streamableRegion; // The following code converts the ImageRegion (templated over dimension) // into an ImageIORegion (not templated over dimension). ImageRegionType imageRequestedRegion = out->GetRequestedRegion(); ImageIORegion ioRequestedRegion(TOutputImage::ImageDimension); using ImageIOAdaptor = ImageIORegionAdaptor; ImageIOAdaptor::Convert(imageRequestedRegion, ioRequestedRegion, largestRegion.GetIndex()); // Tell the IO if we should use streaming while reading m_ImageIO->SetUseStreamedReading(m_UseStreaming); // Delegate to the ImageIO the computation of how the // requested region must be enlarged. m_ActualIORegion = m_ImageIO->GenerateStreamableReadRegionFromRequestedRegion(ioRequestedRegion); // the m_ActualIORegion may be more dimensions then the output // Image, in which case we still need to read this larger region to // support reading the "first slice" of a larger image // see bug 9212 // convert the IORegion to an ImageRegion (which is dimension templated) // if the ImageIO must read a higher dimension region, this will // truncate the last dimensions ImageIOAdaptor::Convert(m_ActualIORegion, streamableRegion, largestRegion.GetIndex()); // Check whether the imageRequestedRegion is fully contained inside the // streamable region. Since, ImageRegion::IsInside regards zero // sized regions, as not being inside any other region, we must // specially check this condition to enable zero sized regions to // pass the region propagation phase of the pipeline. if (!streamableRegion.IsInside(imageRequestedRegion) && imageRequestedRegion.GetNumberOfPixels() != 0) { // we must use an InvalidRequestedRegionError since // DataObject::PropagateRequestedRegion() has an exception // specification std::ostringstream message; message << "ImageIO returns IO region that does not fully contain the requested region. Requested region: " << imageRequestedRegion << "StreamableRegion region: " << streamableRegion; InvalidRequestedRegionError e(__FILE__, __LINE__); e.SetLocation(ITK_LOCATION); e.SetDescription(message.str().c_str()); throw e; } itkDebugMacro("RequestedRegion is set to:" << streamableRegion << " while the m_ActualIORegion is: " << m_ActualIORegion); out->SetRequestedRegion(streamableRegion); } template void ImageFileReader::GenerateData() { this->UpdateProgress(0.0f); typename TOutputImage::Pointer output = this->GetOutput(); itkDebugMacro("ImageFileReader::GenerateData() \n" << "Allocating the buffer with the EnlargedRequestedRegion \n" << output->GetRequestedRegion() << '\n'); // allocated the output image to the size of the enlarge requested region this->AllocateOutputs(); // Test if the file exists and if it can be opened. // An exception will be thrown otherwise, since we can't // successfully read the file. We catch the exception because some // ImageIO's may not actually open a file. Still // reports file error if no ImageIO is loaded. try { m_ExceptionMessage = ""; this->TestFileExistanceAndReadability(); } catch (const itk::ExceptionObject & err) { m_ExceptionMessage = err.GetDescription(); } // Tell the ImageIO to read the file m_ImageIO->SetFileName(this->GetFileName().c_str()); itkDebugMacro("Setting imageIO IORegion to: " << m_ActualIORegion); m_ImageIO->SetIORegion(m_ActualIORegion); // the size of the buffer is computed based on the actual number of // pixels to be read and the actual size of the pixels to be read // (as opposed to the sizes of the output) size_t sizeOfActualIORegion = m_ActualIORegion.GetNumberOfPixels() * (m_ImageIO->GetComponentSize() * m_ImageIO->GetNumberOfComponents()); IOComponentEnum ioType = ImageIOBase::MapPixelType::CType; if (m_ImageIO->GetComponentType() != ioType || (m_ImageIO->GetNumberOfComponents() != ConvertPixelTraits::GetNumberOfComponents())) { // the pixel types don't match so a type conversion needs to be // performed itkDebugMacro("Buffer conversion required from: " << m_ImageIO->GetComponentTypeAsString(m_ImageIO->GetComponentType()) << " to: " << m_ImageIO->GetComponentTypeAsString(ioType) << " ConvertPixelTraits::NumComponents " << ConvertPixelTraits::GetNumberOfComponents() << " m_ImageIO->NumComponents " << m_ImageIO->GetNumberOfComponents()); const auto loadBuffer = make_unique_for_overwrite(sizeOfActualIORegion); m_ImageIO->Read(static_cast(loadBuffer.get())); // See note below as to why the buffered region is needed and // not actualIORegion this->DoConvertBuffer(static_cast(loadBuffer.get()), output->GetBufferedRegion().GetNumberOfPixels()); } else if (m_ActualIORegion.GetNumberOfPixels() != output->GetBufferedRegion().GetNumberOfPixels()) { // NOTE: // for the number of pixels read and the number of pixels // requested to not match, the dimensions of the two regions may // be different, therefore we buffer and copy the pixels itkDebugMacro("Buffer required because file dimension is greater then image dimension"); OutputImagePixelType * outputBuffer = output->GetPixelContainer()->GetBufferPointer(); const auto loadBuffer = make_unique_for_overwrite(sizeOfActualIORegion); m_ImageIO->Read(static_cast(loadBuffer.get())); // we use std::copy_n here as it should be optimized to memcpy for // plain old data, but still is object oriented programming std::copy_n(reinterpret_cast(loadBuffer.get()), output->GetBufferedRegion().GetNumberOfPixels(), outputBuffer); } else { itkDebugMacro("No buffer conversion required."); OutputImagePixelType * outputBuffer = output->GetPixelContainer()->GetBufferPointer(); m_ImageIO->Read(outputBuffer); } this->UpdateProgress(1.0f); } template void ImageFileReader::DoConvertBuffer(const void * inputData, size_t numberOfPixels) { // get the pointer to the destination buffer OutputImagePixelType * outputData = this->GetOutput()->GetPixelContainer()->GetBufferPointer(); bool isVectorImage(strcmp(this->GetOutput()->GetNameOfClass(), "VectorImage") == 0); // TODO: // Pass down the PixelType (RGB, VECTOR, etc.) so that any vector to // scalar conversion be type specific. i.e. RGB to scalar would use // a formula to convert to luminance, VECTOR to scalar would use // vector magnitude. // Create a macro as this code is a bit lengthy and repetitive // if the ImageIO pixel type is typeid(type) then use the ConvertPixelBuffer // class to convert the data block to TOutputImage's pixel type // see DefaultConvertPixelTraits and ConvertPixelBuffer // The first else if block applies only to images of type itk::VectorImage // VectorImage needs to copy out the buffer differently.. The buffer is of // type InternalPixelType, but each pixel is really 'k' consecutive pixels. #define ITK_CONVERT_BUFFER_IF_BLOCK(_CType, type) \ else if (m_ImageIO->GetComponentType() == _CType) \ { \ if (isVectorImage) \ { \ ConvertPixelBuffer::ConvertVectorImage( \ static_cast(inputData), m_ImageIO->GetNumberOfComponents(), outputData, numberOfPixels); \ } \ else \ { \ ConvertPixelBuffer::Convert( \ static_cast(inputData), m_ImageIO->GetNumberOfComponents(), outputData, numberOfPixels); \ } \ } if (false) { } ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::UCHAR, unsigned char) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::CHAR, char) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::USHORT, unsigned short) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::SHORT, short) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::UINT, unsigned int) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::INT, int) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::ULONG, unsigned long) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::LONG, long) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::ULONGLONG, unsigned long long) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::LONGLONG, long long) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::FLOAT, float) ITK_CONVERT_BUFFER_IF_BLOCK(IOComponentEnum::DOUBLE, double) else { #define TYPENAME(x) m_ImageIO->GetComponentTypeAsString(ImageIOBase::MapPixelType::CType) ImageFileReaderException e(__FILE__, __LINE__); std::ostringstream msg; msg << "Couldn't convert component type: " << std::endl << " " << m_ImageIO->GetComponentTypeAsString(m_ImageIO->GetComponentType()) << std::endl << "to one of: " << std::endl << " " << TYPENAME(unsigned char) << std::endl << " " << TYPENAME(char) << std::endl << " " << TYPENAME(unsigned short) << std::endl << " " << TYPENAME(short) << std::endl << " " << TYPENAME(unsigned int) << std::endl << " " << TYPENAME(int) << std::endl << " " << TYPENAME(unsigned long) << std::endl << " " << TYPENAME(long) << std::endl << " " << TYPENAME(unsigned long long) << std::endl << " " << TYPENAME(long long) << std::endl << " " << TYPENAME(float) << std::endl << " " << TYPENAME(double) << std::endl; e.SetDescription(msg.str().c_str()); e.SetLocation(ITK_LOCATION); throw e; } #undef ITK_CONVERT_BUFFER_IF_BLOCK } } // namespace itk #endif