/*========================================================================= * * 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 itkImageSeriesWriter_hxx #define itkImageSeriesWriter_hxx #include "itkDataObject.h" #include "itkImageIOFactory.h" #include "itkIOCommon.h" #include "itkProgressReporter.h" #include "itkImageAlgorithm.h" #include "itkMetaDataObject.h" #include "itkArray.h" #include "vnl/algo/vnl_determinant.h" #include namespace itk { //--------------------------------------------------------- template ImageSeriesWriter::ImageSeriesWriter() : m_ImageIO(nullptr) , m_SeriesFormat("%d") { m_UseCompression = false; } //--------------------------------------------------------- template void ImageSeriesWriter::SetInput(const InputImageType * input) { // ProcessObject is not const_correct so this cast is required here. this->ProcessObject::SetNthInput(0, const_cast(input)); } //--------------------------------------------------------- template auto ImageSeriesWriter::GetInput() -> const InputImageType * { return itkDynamicCastInDebugMode(this->GetPrimaryInput()); } //--------------------------------------------------------- template auto ImageSeriesWriter::GetInput(unsigned int idx) -> const InputImageType * { return itkDynamicCastInDebugMode(this->ProcessObject::GetInput(idx)); } //--------------------------------------------------------- template void ImageSeriesWriter::Write() { const InputImageType * inputImage = this->GetInput(); itkDebugMacro("Writing an image file"); // Make sure input is available if (inputImage == nullptr) { itkExceptionMacro("No input to writer!"); } // Make sure the data is up-to-date. // NOTE: this const_cast<> is due to the lack of const-correctness // of the ProcessObject. auto * nonConstImage = const_cast(inputImage); nonConstImage->Update(); // Notify start event observers this->InvokeEvent(StartEvent()); // Actually do something this->GenerateData(); // Notify end event observers this->InvokeEvent(EndEvent()); // Release upstream data if requested if (inputImage->ShouldIReleaseData()) { nonConstImage->ReleaseData(); } } //--------------------------------------------------------- template void ImageSeriesWriter::GenerateNumericFileNamesAndWrite() { itkWarningMacro("This functionality has been DEPRECATED. Use NumericSeriesFileName for generating the filenames"); this->GenerateNumericFileNames(); this->WriteFiles(); } //--------------------------------------------------------- template void ImageSeriesWriter::GenerateNumericFileNames() { const InputImageType * inputImage = this->GetInput(); if (!inputImage) { itkExceptionMacro("Input image is nullptr"); } m_FileNames.clear(); // We need two regions. One for the input, one for the output. ImageRegion inRegion = inputImage->GetRequestedRegion(); SizeValueType fileNumber = this->m_StartIndex; char fileName[IOCommon::ITK_MAXPATHLEN + 1]; // Compute the number of files to be generated unsigned int numberOfFiles = 1; for (unsigned int n = TOutputImage::ImageDimension; n < TInputImage::ImageDimension; ++n) { numberOfFiles *= inRegion.GetSize(n); } for (unsigned int slice = 0; slice < numberOfFiles; ++slice) { snprintf(fileName, IOCommon::ITK_MAXPATHLEN + 1, m_SeriesFormat.c_str(), fileNumber); m_FileNames.push_back(fileName); fileNumber += this->m_IncrementIndex; } } //--------------------------------------------------------- template void ImageSeriesWriter::GenerateData() { itkDebugMacro("Writing a series of files"); if (m_FileNames.empty()) { // this method will be deprecated. It is here only to maintain the old API this->GenerateNumericFileNamesAndWrite(); return; } this->WriteFiles(); } //--------------------------------------------------------- template void ImageSeriesWriter::WriteFiles() { const InputImageType * inputImage = this->GetInput(); if (!inputImage) { itkExceptionMacro("Input image is nullptr"); } // We need two regions. One for the input, one for the output. ImageRegion inRegion = inputImage->GetRequestedRegion(); ImageRegion outRegion; // The size of the output will match the input sizes, up to the // dimension of the input. for (unsigned int i = 0; i < TOutputImage::ImageDimension; ++i) { outRegion.SetSize(i, inputImage->GetRequestedRegion().GetSize()[i]); } // Allocate an image for output and create an iterator for it auto outputImage = OutputImageType::New(); outputImage->SetRegions(outRegion); outputImage->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel()); outputImage->Allocate(); // Set the origin and spacing of the output double spacing[TOutputImage::ImageDimension]; double origin[TOutputImage::ImageDimension]; typename TOutputImage::DirectionType direction; for (unsigned int i = 0; i < TOutputImage::ImageDimension; ++i) { origin[i] = inputImage->GetOrigin()[i]; spacing[i] = inputImage->GetSpacing()[i]; outRegion.SetSize(i, inputImage->GetRequestedRegion().GetSize()[i]); for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j) { direction[j][i] = inputImage->GetDirection()[j][i]; } } // // Address the fact that when taking a 2x2 sub-matrix from // the direction matrix we may obtain a singular matrix. // A 2x2 orientation can't represent a 3x3 orientation and // therefore, replacing the orientation with an identity // is as arbitrary as any other choice. // if (vnl_determinant(direction.GetVnlMatrix()) == 0.0) { direction.SetIdentity(); } outputImage->SetOrigin(origin); outputImage->SetSpacing(spacing); outputImage->SetDirection(direction); Index inIndex; Size inSize; SizeValueType pixelsPerFile = outputImage->GetRequestedRegion().GetNumberOfPixels(); inSize.Fill(1); for (unsigned int ns = 0; ns < TOutputImage::ImageDimension; ++ns) { inSize[ns] = outRegion.GetSize()[ns]; } unsigned int expectedNumberOfFiles = 1; for (unsigned int n = TOutputImage::ImageDimension; n < TInputImage::ImageDimension; ++n) { expectedNumberOfFiles *= inRegion.GetSize(n); } if (m_FileNames.size() != expectedNumberOfFiles) { itkExceptionMacro("The number of filenames passed is " << m_FileNames.size() << " but " << expectedNumberOfFiles << " were expected "); } itkDebugMacro("Number of files to write = " << m_FileNames.size()); ProgressReporter progress(this, 0, expectedNumberOfFiles, expectedNumberOfFiles); // For each "slice" in the input, copy the region to the output, // build a filename and write the file. typename InputImageType::OffsetValueType offset = 0; for (unsigned int slice = 0; slice < m_FileNames.size(); ++slice) { // Select a "slice" of the image. inIndex = inputImage->ComputeIndex(offset); inRegion.SetIndex(inIndex); inRegion.SetSize(inSize); // Copy the selected "slice" into the output image. ImageAlgorithm::Copy(inputImage, outputImage.GetPointer(), inRegion, outRegion); auto writer = WriterType::New(); writer->UseInputMetaDataDictionaryOff(); // use the dictionary from the // ImageIO class writer->SetInput(outputImage); if (m_ImageIO) { writer->SetImageIO(m_ImageIO); } if (m_MetaDataDictionaryArray) { if (m_ImageIO) { if (slice > m_MetaDataDictionaryArray->size() - 1) { itkExceptionMacro("The slice number: " << slice + 1 << " exceeds the size of the MetaDataDictionaryArray " << m_MetaDataDictionaryArray->size() << '.'); } DictionaryRawPointer dictionary = (*m_MetaDataDictionaryArray)[slice]; m_ImageIO->SetMetaDataDictionary((*dictionary)); } else { itkExceptionMacro("Attempted to use a MetaDataDictionaryArray without specifying an ImageIO!"); } } else { if (m_ImageIO) { DictionaryType & dictionary = m_ImageIO->GetMetaDataDictionary(); typename InputImageType::SpacingType spacing2 = inputImage->GetSpacing(); // origin of the output slice in the // n-dimensional space of the input image. typename InputImageType::PointType origin2; inputImage->TransformIndexToPhysicalPoint(inIndex, origin2); const unsigned int inputImageDimension = TInputImage::ImageDimension; using DoubleArrayType = Array; DoubleArrayType originArray(inputImageDimension); DoubleArrayType spacingArray(inputImageDimension); for (unsigned int d = 0; d < inputImageDimension; ++d) { originArray[d] = origin2[d]; spacingArray[d] = spacing2[d]; } EncapsulateMetaData(dictionary, ITK_Origin, originArray); EncapsulateMetaData(dictionary, ITK_Spacing, spacingArray); EncapsulateMetaData(dictionary, ITK_NumberOfDimensions, inputImageDimension); typename InputImageType::DirectionType direction2 = inputImage->GetDirection(); using DoubleMatrixType = Matrix; DoubleMatrixType directionMatrix; for (unsigned int i = 0; i < inputImageDimension; ++i) { for (unsigned int j = 0; j < inputImageDimension; ++j) { directionMatrix[j][i] = direction2[i][j]; } } EncapsulateMetaData(dictionary, ITK_ZDirection, directionMatrix); } } writer->SetFileName(m_FileNames[slice].c_str()); writer->SetUseCompression(m_UseCompression); writer->Update(); progress.CompletedPixel(); offset += pixelsPerFile; } } //--------------------------------------------------------- template void ImageSeriesWriter::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); itkPrintSelfObjectMacro(ImageIO); os << indent << "StartIndex: " << m_StartIndex << std::endl; os << indent << "IncrementIndex: " << m_IncrementIndex << std::endl; os << indent << "SeriesFormat: " << m_SeriesFormat << std::endl; os << indent << "MetaDataDictionaryArray: " << m_MetaDataDictionaryArray << std::endl; if (m_UseCompression) { os << indent << "Compression: On\n"; } else { os << indent << "Compression: Off\n"; } } } // end namespace itk #endif