diff --git a/Modules/IO/ImageIO/include/otbMultiImageFileWriter.h b/Modules/IO/ImageIO/include/otbMultiImageFileWriter.h new file mode 100644 index 0000000000000000000000000000000000000000..ca2601b32f9f9b15167f7fbede74c48a02a49709 --- /dev/null +++ b/Modules/IO/ImageIO/include/otbMultiImageFileWriter.h @@ -0,0 +1,322 @@ +/* + * Copyright (C) CS SI + * + * This file is part of Orfeo Toolbox + * + * https://www.orfeo-toolbox.org/ + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 otbMultiImageFileWriter_h +#define otbMultiImageFileWriter_h + +#include "otbImageFileWriter.h" +#include "itkImageBase.h" +#include "itkProcessObject.h" +#include "itkImageIOBase.h" + +//#include "s2ipfImageRegionFixedSplitter.h" + +#include <boost/shared_ptr.hpp> + +namespace otb +{ + +/** \class MultiImageFileWriter + * \brief Streams a pipeline with multiple outputs. + * It optionally writes each output to a file, and can also separate each + * output into several granules files (use SetGranuleGenerationMode). Inputs + * are connected to the writer using the AddInput method. + * The streaming occurs by strips. Each output may have a different resolution, + * specified at the AddInput call, and the strip size is proportional to the + * resolution. + */ +class MultiImageFileWriter: public itk::ProcessObject +{ + friend class Sink; +public: + /** Standard class typedefs. */ + typedef MultiImageFileWriter Self; + typedef itk::ProcessObject Superclass; + typedef itk::SmartPointer<Self> Pointer; + typedef itk::SmartPointer<const Self> ConstPointer; + + itkNewMacro(Self); + + itkTypeMacro(MultiImageFileWriter, itk::ProcessObject); + + /** Public typedefs */ + typedef itk::ImageBase<2> ImageBaseType; + typedef ImageBaseType::RegionType RegionType; + typedef ImageBaseType::IndexType IndexType; + typedef ImageBaseType::IndexValueType IndexValueType; + typedef ImageBaseType::SizeType SizeType; + typedef ImageBaseType::SizeValueType SizeValueType; + + //typedef ImageRegionFixedSplitter<ImageBaseType::ImageDimension> SplitterType; + + typedef StreamingManager<ImageBaseType> StreamingManagerType; + + /** Return the StreamingManager object responsible for dividing + * the region to write */ + StreamingManagerType* GetStreamingManager(void) + { + return m_StreamingManager; + } + + /** Set a user-specified implementation of StreamingManager + * used to divide the largest possible region in several divisions */ + void SetStreamingManager(StreamingManagerType* streamingManager) + { + m_StreamingManager = streamingManager; + } + + virtual void UpdateOutputData(itk::DataObject * itkNotUsed(output)); + + /** Sets the number of lines used in stripped streaming for an input with + resolutionFactor == 1.0 . This number is actually multiplied by the resolution + factor to obtain the size of the strip for each input image. */ + itkSetMacro(NumberOfLinesPerStrip, int); + itkGetMacro(NumberOfLinesPerStrip, int); + + /** Set the number of rows per granule for an image with resolution factor 1.0 */ + //~ itkSetMacro(NumberOfRowsPerGranule, int); + //~ itkGetMacro(NumberOfRowsPerGranule, int); + + /** Specify whether granules are to be generated. If false, a single file is + generated for each input */ + //~ itkSetMacro(GranuleGenerationMode, bool); + //~ itkGetConstReferenceMacro(GranuleGenerationMode, bool); + //~ itkBooleanMacro(GranuleGenerationMode); + + /** Set the compression On or Off */ + //~ itkSetMacro(UseCompression, bool); + //~ itkGetConstReferenceMacro(UseCompression, bool); + //~ itkBooleanMacro(UseCompression); + + /** + * Enable/disable writing of a .geom file with the ossim keyword list along with the written image + */ + //~ itkSetMacro(WriteGeomFile, bool); + //~ itkGetMacro(WriteGeomFile, bool); + //~ itkBooleanMacro(WriteGeomFile); + + /** Connect a new input to the multi-writer. Only the input pointer is + * required. If the filename list is empty, + * streaming will occur without writing. It the filename list contains more + * than one element, then the output will be divided into this number of + * granule files. The resolution factor specifies the ratio between the height of this image and the + * height of a reference image. The number of lines per strip class parameter will be modified according to this factor, so + * that images with different resolutions can be streamed together. For example, use 1.0 for a 10m pixels image, 0.5 for a 20m + * pixels image, and specify the number of lines per strip according to the 10m pixels image. + * You may specify top and bottom margins that will be removed from the input image, according to its largest possible region. + */ + template <class TImage> + void AddInputImage(const TImage* inputPtr, const std::string & fileName) + { + Sink<TImage> * sink = new Sink<TImage>(inputPtr, fileName); + m_SinkList.push_back(SinkBase::Pointer(sink)); + unsigned int size = m_SinkList.size(); + this->SetNumberOfInputs(size); + this->SetNthInput(size - 1, const_cast<itk::DataObject*>(dynamic_cast<const itk::DataObject*>(inputPtr))); + } + + //~ /** Connect a new input to the multi-writer. Takes a single filename instead + //~ of a list of filenames */ + //~ template <class TImage> + //~ void AddInput(const TImage* inputPtr, const std::string & fileName = std::string(), double resolutionFactor = 1.0, int topMarginTrimSize = 0, int bottomMarginTrimSize = 0) + //~ { + //~ std::vector<std::string> fileNameList; + //~ fileNameList.push_back(fileName); + //~ this->AddInput(inputPtr, fileNameList, resolutionFactor, topMarginTrimSize, bottomMarginTrimSize); + //~ } +//~ + //~ /** This version of AddInput takes no input filename */ + //~ template <class TImage> + //~ void AddInput(const TImage* inputPtr, double resolutionFactor = 1.0, int topMarginTrimSize = 0, int bottomMarginTrimSize = 0) + //~ { + //~ this->AddInput(inputPtr, std::vector<std::string>(), resolutionFactor, topMarginTrimSize, bottomMarginTrimSize); + //~ } + + virtual void UpdateOutputInformation(); + + virtual void Update() + { + this->UpdateOutputInformation(); + this->UpdateOutputData(NULL); + } + +protected: + /** SetInput is changed to protected. Use AddInput to connect the pipeline to + * the writer + */ +// virtual void SetInput(const ImageBaseType* image) { this->Superclass::SetInput(image); } + + /** SetInput is changed to protected. Use AddInput to connect the pipeline to + * the writer + */ +// virtual void SetInput(unsigned int i, const ImageBaseType* image) { this->Superclass::SetInput(i, image); } + + MultiImageFileWriter(); + virtual ~MultiImageFileWriter() {} + + /** GenerateData calls the Write method for each connected input */ + virtual void GenerateData(void); + + virtual void GenerateInputRequestedRegion(); + + /** Computes the number of divisions */ + virtual void InitializeStreaming(); + + /** Goes up the pipeline starting at imagePtr, resetting all requested regions to a null region */ + void ResetAllRequestedRegions(ImageBaseType* imagePtr); + + /** Returns the current stream region of the given input */ + virtual RegionType GetStreamRegion(int inputIndex); + + /** This is the number of lines used in stripped streaming for an input with + resolutionFactor == 1.0 . This number is actually multiplied by the resolution + factor to obtain the size of the strip for each input image. */ + int m_NumberOfLinesPerStrip; + + void operator =(const MultiImageFileWriter&); //purposely not implemented + + void ObserveSourceFilterProgress(itk::Object* object, const itk::EventObject & event) + { + if (typeid(event) != typeid(itk::ProgressEvent)) + { + return; + } + + itk::ProcessObject* processObject = dynamic_cast<itk::ProcessObject*>(object); + if (processObject) + { + m_DivisionProgress = processObject->GetProgress(); + } + + this->UpdateFilterProgress(); + } + + void UpdateFilterProgress() + { + this->UpdateProgress((m_DivisionProgress + m_CurrentDivision) / m_NumberOfDivisions); + } + + //Granule Generation mode + //~ bool m_GranuleGenerationMode; + + //Division parameters + unsigned int m_NumberOfDivisions; + unsigned int m_CurrentDivision; + float m_DivisionProgress; + + bool m_IsObserving; + unsigned long m_ObserverID; + + /** compression */ + //~ bool m_UseCompression; + //~ bool m_UseInputMetaDataDictionary; // whether to use the + // MetaDataDictionary from the + // input or not. + //~ bool m_WriteGeomFile; // Write a geom file to store the kwl + + /** Number of rows per granule for an image with resolution factor 1.0 (i.e. a + * 10m band) + */ + //~ int m_NumberOfRowsPerGranule; + + //~ SplitterType::Pointer m_Splitter; + + class SinkBase + { + public: + SinkBase() {} + SinkBase(ImageBaseType::ConstPointer inputImage) : + m_InputImage(inputImage) + {} + virtual ~SinkBase() {} + virtual ImageBaseType::ConstPointer GetInput() const { return m_InputImage; } + virtual ImageBaseType::Pointer GetInput() { return const_cast<ImageBaseType*>(m_InputImage.GetPointer()); } + virtual void WriteImageInformation() = 0; + virtual void Write(const RegionType & streamRegion) = 0; + virtual bool CanStreamWrite() = 0; + typedef boost::shared_ptr<SinkBase> Pointer; + protected: + /** The image on which streaming is performed */ + ImageBaseType::ConstPointer m_InputImage; + }; + + /** \class Sink + * Parameters and methods specific to a single input + */ + template <class TImage> + class Sink : public SinkBase + { + //~ friend class MultiImageFileWriter; + public: + Sink() {} + Sink(typename TImage::ConstPointer inputImage, + const std::string & filename); + + virtual ~Sink() {} + + virtual void WriteImageInformation(); + virtual void Write(const RegionType & streamRegion); + virtual bool CanStreamWrite(); + typedef boost::shared_ptr<Sink> Pointer; + protected: + //~ void CreateImageFile(int fileIndex); + + /** There may be several output filenames in the case of granule mode + * generation. Or there may be none if no output file is required. + */ + //~ std::string m_FileName; + + /** Specifies whether image is actually written to a file */ + //~ bool m_UseImageIO; + private: + /** Actual writer for this image */ + typename otb::ImageFileWriter<TImage>::Pointer m_Writer; + + /** An ImageIO used to actually write data to a file */ + itk::ImageIOBase::Pointer m_ImageIO; + + /** The current file number into which data is written */ + //~ int m_CurrentFileIndex; + + /** The image region to write to the current file */ + //~ RegionType m_CurrentFileRegion; + + /** The height of the file, which can be the size of a granule in granule + generation mode, or else the size of the input image (minus margins). */ + //~ int m_FileHeight; + + /** A pointer to the writer to gain access to its fields */ + //~ typename MultiImageFileWriter::Pointer m_Writer; + }; + + /** The list of inputs and their associated parameters, built using AddInput */ + typedef std::vector<boost::shared_ptr<SinkBase> > SinkListType; + SinkListType m_SinkList; + + std::vector<RegionType> m_StreamRegionList; + + StreamingManagerType::Pointer m_StreamingManager; +}; + +} // end of namespace s2ipf + +//~ #include "otbMultiImageFileWriter.txx" + +#endif // otbMultiImageFileWriter_h diff --git a/Modules/IO/ImageIO/include/otbMultiImageFileWriter.txx b/Modules/IO/ImageIO/include/otbMultiImageFileWriter.txx new file mode 100644 index 0000000000000000000000000000000000000000..81417a60eaf333a3842f8df83e2ba89d71543547 --- /dev/null +++ b/Modules/IO/ImageIO/include/otbMultiImageFileWriter.txx @@ -0,0 +1,263 @@ +/* + * Copyright (C) CS SI + * + * This file is part of Orfeo Toolbox + * + * https://www.orfeo-toolbox.org/ + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 otbMultiImageFileWriter_txx +#define otbMultiImageFileWriter_txx + +#include "otbMultiImageFileWriter.h" +#include "otbImageIOFactory.h" +//~ #include "itkImageFileWriter.h" +#include "otbMacro.h" +//#include "s2ipfFileUtils.h" + +namespace otb +{ + +template <class TImage> +MultiImageFileWriter::Sink<TImage> +::Sink(typename TImage::ConstPointer inputImage, + const std::string & fileName): + SinkBase(dynamic_cast<const ImageBaseType*>(inputImage.GetPointer())), + //~ m_FileName(fileName), + m_ImageIO(NULL), + m_Writer(otb::ImageFileWriter<TImage>::New()) +{ + m_Writer->SetFileName(fileName); + m_Writer->SetInput(inputImage); +} + + +template <class TImage> +bool +MultiImageFileWriter::Sink<TImage> +::CanStreamWrite() +{ + if (m_ImageIO.IsNull()) + return false; + return m_ImageIO->CanStreamWrite(); +} + +template <class TImage> +void +MultiImageFileWriter::Sink<TImage> +::WriteImageInformation() +{ + m_Writer->UpdateOutputInformation(); + m_ImageIO = m_Writer->GetImageIO(); + + + //~ // new ImageIO: close current ImageIO if exists and open the new one + //~ if (! m_ImageIO.IsNull()) { /* nothing to do to close IO ? */ } + //~ m_CurrentFileIndex = fileIndex; + //~ m_ImageIO = ImageIOFactory::CreateImageIO( m_FileNameList[fileIndex].c_str(), otb::ImageIOFactory::WriteMode ); +//~ + //~ if (m_ImageIO.IsNull()) + //~ { + //~ itk::ImageFileWriterException e(__FILE__, __LINE__); + //~ std::ostringstream msg; + //~ msg << " Could not create IO object for file " << m_FileNameList[fileIndex].c_str() << std::endl; + //~ msg << " Tried to create one of the following:" << std::endl; + //~ std::list<itk::LightObject::Pointer> allobjects = itk::ObjectFactoryBase::CreateAllInstance("itkImageIOBase"); + //~ for (std::list<itk::LightObject::Pointer>::iterator i = allobjects.begin(); i != allobjects.end(); ++i) + //~ { + //~ itk::ImageIOBase* io = dynamic_cast<itk::ImageIOBase*>(i->GetPointer()); + //~ msg << " " << io->GetNameOfClass() << std::endl; + //~ } + //~ msg << " You probably failed to set a file suffix, or" << std::endl; + //~ msg << " set the suffix to an unsupported type." << std::endl; + //~ e.SetDescription(msg.str().c_str()); + //~ e.SetLocation(ITK_LOCATION); + //~ throw e; + //~ } +//~ + //~ m_ImageIO->SetFileName(m_FileNameList[fileIndex]); + //~ if( ! m_ImageIO->CanStreamWrite() ) + //~ { + //~ std::ostringstream oss; + //~ oss << " The ImageFactory selected for the image file <" << m_FileNameList[fileIndex] << "> does not support streaming."; + //~ throw std::runtime_error(oss.str()); + //~ } + //~ m_ImageIO->SetNumberOfDimensions(TImage::ImageDimension); + //~ // Set the region of data to write to the output file (independently of + //~ // current stream region) + //~ m_CurrentFileRegion = m_InputImage->GetLargestPossibleRegion(); + //~ m_CurrentFileRegion.SetIndex(TImage::ImageDimension - 1, fileIndex * m_FileHeight + m_TopMarginTrimSize); + //~ m_CurrentFileRegion.SetSize(TImage::ImageDimension - 1, m_FileHeight); +//~ + //~ const typename TImage::SpacingType& spacing = m_InputImage->GetSpacing(); + //~ const typename TImage::PointType& origin = m_InputImage->GetOrigin(); + //~ const typename TImage::DirectionType& direction = m_InputImage->GetDirection(); +//~ + //~ for (unsigned int i = 0; i < TImage::ImageDimension; ++i) + //~ { + //~ // Final image size + //~ m_ImageIO->SetDimensions(i, m_CurrentFileRegion.GetSize(i)); + //~ m_ImageIO->SetSpacing(i, spacing[i]); + //~ m_ImageIO->SetOrigin(i, origin[i]); // TODO: fix origin for each file + //~ vnl_vector<double> axisDirection(TImage::ImageDimension); + //~ // Please note: direction cosines are stored as columns of the + //~ // direction matrix + //~ for (unsigned int j = 0; j < TImage::ImageDimension; ++j) + //~ { + //~ axisDirection[j] = direction[j][i]; + //~ } + //~ m_ImageIO->SetDirection(i, axisDirection); + //~ } + //~ m_ImageIO->SetUseCompression(m_Writer->m_UseCompression); +//~ /* + //~ if (! hasHDRExtension(m_FileNameList[fileIndex])) // This test will disable the creation of a .raw.aux.xml file in the case of raw/hdr format + //~ { + //~ m_ImageIO->SetMetaDataDictionary(m_InputImage->GetMetaDataDictionary()); + //~ } +//~ */ + //~ m_ImageIO->SetMetaDataDictionary(m_InputImage->GetMetaDataDictionary()); +//~ + //~ /** Create Image file */ + //~ m_ImageIO->WriteImageInformation(); +//~ + //~ if (strcmp(m_InputImage->GetNameOfClass(), "VectorImage") == 0) + //~ { + //~ typedef typename TImage::InternalPixelType VectorImagePixelType; + //~ m_ImageIO->SetPixelTypeInfo(typeid(VectorImagePixelType)); +//~ + //~ typedef typename TImage::AccessorFunctorType AccessorFunctorType; + //~ const TImage* inputImage = dynamic_cast<const TImage*>(m_InputImage.GetPointer()); + //~ m_ImageIO->SetNumberOfComponents(AccessorFunctorType::GetVectorLength(inputImage)); + //~ } + //~ else + //~ { + //~ // Set the pixel and component type; the number of components. + //~ typedef typename TImage::PixelType ImagePixelType; + //~ m_ImageIO->SetPixelTypeInfo(typeid(ImagePixelType)); + //~ } + + +} + +template <class TImage> +void +MultiImageFileWriter::Sink<TImage> +::Write(const RegionType & streamRegion) +{ + // Write the whole image + itk::ImageIORegion ioRegion(TImage::ImageDimension); + for (unsigned int i = 0; i < TImage::ImageDimension; ++i) + { + ioRegion.SetSize(i, streamRegion.GetSize(i)); + ioRegion.SetIndex(i, streamRegion.GetIndex(i)); + //Set the ioRegion index using the shifted index ( (0,0 without box parameter)) + //~ ioRegion.SetIndex(i, streamRegion.GetIndex(i) - m_ShiftOutputIndex[i]); + } + //~ this->SetIORegion(ioRegion); + m_ImageIO->SetIORegion(ioRegion); + m_Writer->GenerateData(); + + //~ if( ! m_UseImageIO ) return; +//~ + //~ if(!m_Writer->GetGranuleGenerationMode() && m_FileNameList.size() > 1) + //~ { + //~ throw std::runtime_error( "There must be at most one output image filename when granule generation mode is disabled" ); + //~ } +//~ + //~ // Find first file into which streamRegion should write + //~ const IndexType & streamIndex = streamRegion.GetIndex(); + //~ const SizeType & streamSize = streamRegion.GetSize(); +//~ +//~ + //~ int firstFileIndex = 0; + //~ int lastFileIndex = 0; + //~ if(m_Writer->m_GranuleGenerationMode) + //~ { + //~ firstFileIndex = (streamIndex[1] - m_TopMarginTrimSize) / (m_Writer->m_NumberOfRowsPerGranule * m_ResolutionFactor); + //~ lastFileIndex = (streamIndex[1] - m_TopMarginTrimSize + streamSize[1] - 1) / (m_Writer->m_NumberOfRowsPerGranule * m_ResolutionFactor); + //~ // If lastFileIndex is greater than the index of the last filename, then use + //~ // the last filename + //~ lastFileIndex = std::min<int>(lastFileIndex, m_FileNameList.size() - 1); + //~ } +//~ + //~ m_FileHeight = (m_Writer->m_GranuleGenerationMode)?m_Writer->m_NumberOfRowsPerGranule * m_ResolutionFactor : (m_InputImage->GetLargestPossibleRegion().GetSize(1) - m_TopMarginTrimSize - m_BottomMarginTrimSize); +//~ + //~ for( int fileIndex = firstFileIndex; fileIndex <= lastFileIndex; ++fileIndex ) + //~ { + //~ // Create the image file only if the imageIO is not already open for this file + //~ if(m_CurrentFileIndex != fileIndex) + //~ { + //~ CreateImageFile(fileIndex); + //~ } + //~ // Write part of streamRegion matching m_CurrentFileRegion + //~ IndexValueType streamLastIndex = streamRegion.GetIndex(TImage::ImageDimension-1) + streamRegion.GetSize(TImage::ImageDimension - 1) - 1; + //~ IndexValueType fileLastIndex = m_CurrentFileRegion.GetIndex(TImage::ImageDimension-1) + m_CurrentFileRegion.GetSize(TImage::ImageDimension - 1) - 1; + //~ RegionType streamRegionForCurrentFile = streamRegion; + //~ IndexValueType streamStartIndexForCurrentFile = std::max( + //~ streamRegion.GetIndex(TImage::ImageDimension - 1), + //~ m_CurrentFileRegion.GetIndex(TImage::ImageDimension - 1) + //~ ); + //~ streamRegionForCurrentFile.SetIndex(TImage::ImageDimension - 1, streamStartIndexForCurrentFile ); + //~ streamRegionForCurrentFile.SetSize( + //~ TImage::ImageDimension-1, std::min( + //~ streamLastIndex - streamStartIndexForCurrentFile + 1, + //~ fileLastIndex - streamStartIndexForCurrentFile + 1 + //~ ) + //~ ); +//~ + //~ const TImage* inputImage = dynamic_cast<const TImage*>(m_InputImage.GetPointer()); + //~ const void* dataPtr = (const void*) inputImage->GetBufferPointer(); +//~ + //~ itk::ImageIORegion ioRegion(TImage::ImageDimension); + //~ itk::ImageIORegionAdaptor<TImage::ImageDimension>::Convert(streamRegionForCurrentFile, ioRegion, m_CurrentFileRegion.GetIndex()); + //~ m_ImageIO->SetIORegion(ioRegion); +//~ + //~ RegionType bufferedRegion = m_InputImage->GetBufferedRegion(); +//~ + //~ typename TImage::Pointer cacheImage; + //~ // before this test, bad stuff would happen when they don't match + //~ if (bufferedRegion != streamRegionForCurrentFile) + //~ { + //~ otbGenericMsgDebugMacro("Requested stream region does not match generated output"); + //~ otbGenericMsgDebugMacro("Extracting output region from buffered region"); +//~ + //~ cacheImage = TImage::New(); + //~ cacheImage->CopyInformation(m_InputImage); + //~ cacheImage->SetBufferedRegion(streamRegionForCurrentFile); + //~ cacheImage->Allocate(); + //~ typedef itk::ImageRegionConstIterator<TImage> ConstIteratorType; + //~ typedef itk::ImageRegionIterator<TImage> IteratorType; +//~ + //~ ConstIteratorType in(inputImage, streamRegionForCurrentFile); + //~ IteratorType out(cacheImage, streamRegionForCurrentFile); +//~ + //~ // copy the data into a buffer to match the ioregion + //~ for (in.GoToBegin(), out.GoToBegin(); !in.IsAtEnd(); ++in, ++out) + //~ { + //~ out.Set(in.Get()); + //~ } +//~ + //~ dataPtr = (const void*) cacheImage->GetBufferPointer(); +//~ + //~ } +//~ + //~ // Write to file + //~ m_ImageIO->Write(dataPtr); + //~ } +} + +} // end of namespace otb + +#endif // otbMultiImageFileWriter_txx diff --git a/Modules/IO/ImageIO/src/otbMultiImageFileWriter.cxx b/Modules/IO/ImageIO/src/otbMultiImageFileWriter.cxx new file mode 100644 index 0000000000000000000000000000000000000000..c6fdff5c65acb7620059a9bfb5a05f1a099a4b25 --- /dev/null +++ b/Modules/IO/ImageIO/src/otbMultiImageFileWriter.cxx @@ -0,0 +1,421 @@ +/* + * Copyright (C) CS SI + * + * This file is part of Orfeo Toolbox + * + * https://www.orfeo-toolbox.org/ + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +#include "otbMultiImageFileWriter.h" +#include "otbImage.h" +#include "otbImageIOFactory.h" +//~ #include "itkImageFileWriter.h" + +//#include "s2ipfSensor.h" +//~ #define S2IPF_GRANULE_HEIGHT 2304 + +namespace otb +{ + +MultiImageFileWriter +::MultiImageFileWriter() : + m_NumberOfLinesPerStrip(600), + //~ m_GranuleGenerationMode(false), + m_NumberOfDivisions(0), + m_CurrentDivision(0), + m_DivisionProgress(0.0), + m_IsObserving(true), + m_ObserverID(0) + //~ m_UseCompression(false), + //~ m_UseInputMetaDataDictionary(false), + //~ m_WriteGeomFile(false), + //~ m_NumberOfRowsPerGranule(S2IPF_GRANULE_HEIGHT) +{ + // By default, we use tiled streaming, with automatic tile size + // We don't set any parameter, so the memory size is retrieved from the OTB configuration options + this->SetAutomaticAdaptativeStreaming(); + // add a fake output to drive memory estimation + this->SetNthOutput(0, otb::Image<unsigned char, 2>::New()); +} + +void +MultiImageFileWriter +::SetNumberOfDivisionsStrippedStreaming(unsigned int nbDivisions) +{ + typedef NumberOfDivisionsStrippedStreamingManager<ImageBaseType> NumberOfDivisionsStrippedStreamingManagerType; + typename NumberOfDivisionsStrippedStreamingManagerType::Pointer streamingManager = NumberOfDivisionsStrippedStreamingManagerType::New(); + streamingManager->SetNumberOfDivisions(nbDivisions); + + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetNumberOfDivisionsTiledStreaming(unsigned int nbDivisions) +{ + typedef NumberOfDivisionsTiledStreamingManager<ImageBaseType> NumberOfDivisionsTiledStreamingManagerType; + typename NumberOfDivisionsTiledStreamingManagerType::Pointer streamingManager = NumberOfDivisionsTiledStreamingManagerType::New(); + streamingManager->SetNumberOfDivisions(nbDivisions); + + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetNumberOfLinesStrippedStreaming(unsigned int nbLinesPerStrip) +{ + typedef NumberOfLinesStrippedStreamingManager<ImageBaseType> NumberOfLinesStrippedStreamingManagerType; + typename NumberOfLinesStrippedStreamingManagerType::Pointer streamingManager = NumberOfLinesStrippedStreamingManagerType::New(); + streamingManager->SetNumberOfLinesPerStrip(nbLinesPerStrip); + + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetAutomaticStrippedStreaming(unsigned int availableRAM, double bias) +{ + typedef RAMDrivenStrippedStreamingManager<ImageBaseType> RAMDrivenStrippedStreamingManagerType; + typename RAMDrivenStrippedStreamingManagerType::Pointer streamingManager = RAMDrivenStrippedStreamingManagerType::New(); + streamingManager->SetAvailableRAMInMB(availableRAM); + streamingManager->SetBias(bias); + + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetTileDimensionTiledStreaming(unsigned int tileDimension) +{ + typedef TileDimensionTiledStreamingManager<ImageBaseType> TileDimensionTiledStreamingManagerType; + typename TileDimensionTiledStreamingManagerType::Pointer streamingManager = TileDimensionTiledStreamingManagerType::New(); + streamingManager->SetTileDimension(tileDimension); + + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetAutomaticTiledStreaming(unsigned int availableRAM, double bias) +{ + typedef RAMDrivenTiledStreamingManager<ImageBaseType> RAMDrivenTiledStreamingManagerType; + typename RAMDrivenTiledStreamingManagerType::Pointer streamingManager = RAMDrivenTiledStreamingManagerType::New(); + streamingManager->SetAvailableRAMInMB(availableRAM); + streamingManager->SetBias(bias); + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::SetAutomaticAdaptativeStreaming(unsigned int availableRAM, double bias) +{ + typedef RAMDrivenAdaptativeStreamingManager<ImageBaseType> RAMDrivenAdaptativeStreamingManagerType; + typename RAMDrivenAdaptativeStreamingManagerType::Pointer streamingManager = RAMDrivenAdaptativeStreamingManagerType::New(); + streamingManager->SetAvailableRAMInMB(availableRAM); + streamingManager->SetBias(bias); + m_StreamingManager = streamingManager; +} + +void +MultiImageFileWriter +::InitializeStreaming() +{ +// const ImageBaseType* inputPtr = this->GetInput(0); + if(m_SinkList.size() == 0) + itkExceptionMacro("At least one input must be connected to the writer\n"); + const ImageBaseType* inputPtr = m_SinkList[0]->GetInput(); + if(!inputPtr) + itkExceptionMacro("At least one input must be connected to the writer\n"); + + RegionType region = inputPtr->GetLargestPossibleRegion(); + // TODO : setup streaming for every input + /** + * Determine of number of pieces to divide the input. This will be the + * minimum of what the user specified via SetNumberOfDivisionsStrippedStreaming() + * and what the Splitter thinks is a reasonable value. + */ + + /** Control if the ImageIO is CanStreamWrite */ + bool canStream = true; + bool isBuffered = true; + for (unsigned int inputIndex = 0; inputIndex < m_SinkList.size(); ++inputIndex) + { + if (!m_SinkList[inputIndex]->CanStreamWrite()) + { + canStream = false; + } + if (m_SinkList[inputIndex]->GetInput()->GetBufferedRegion() != + m_SinkList[inputIndex]->GetInput()->GetLargestPossibleRegion()) + { + isBuffered = false; + } + } + if (canStream == false) + { + otbWarningMacro( + << "The ImageFactory selected for the image file <" << m_FileName.c_str() << + "> does not support streaming."); + this->SetNumberOfDivisionsStrippedStreaming(1); + } + + /** Compare the buffered region with the inputRegion which is the largest + * possible region or a user defined region through extended filename + * Not sure that if this modification is needed */ + else if (isBuffered) + { + otbMsgDevMacro(<< "Buffered region is the largest possible region, there is no need for streaming."); + this->SetNumberOfDivisionsStrippedStreaming(1); + } + // TODO : instead of inputPtr, use an output of MultiImageWriter + m_StreamingManager->PrepareStreaming(inputPtr, inputRegion); + m_NumberOfDivisions = m_StreamingManager->GetNumberOfSplits(); + otbMsgDebugMacro(<< "Number Of Stream Divisions : " << m_NumberOfDivisions); +} + +void +MultiImageFileWriter +::ResetAllRequestedRegions(ImageBaseType* imagePtr) +{ + RegionType nullRegion = imagePtr->GetLargestPossibleRegion(); + nullRegion.SetSize(0, 0); + nullRegion.SetSize(1, 0); + + imagePtr->SetRequestedRegion(nullRegion); + if(imagePtr->GetSource()) + { + itk::ProcessObject::DataObjectPointerArray inputs = imagePtr->GetSource()->GetInputs(); + for( itk::ProcessObject::DataObjectPointerArray::iterator + it = inputs.begin(); + it != inputs.end(); + ++it ) + { + ImageBaseType * inputImagePtr = dynamic_cast<ImageBaseType*>(it->GetPointer()); + if(inputImagePtr != NULL) + { + ResetAllRequestedRegions(inputImagePtr); + } + } + } +} + +void +MultiImageFileWriter +::UpdateOutputInformation() +{ + for(unsigned int inputIndex = 0; inputIndex < m_SinkList.size(); ++inputIndex) + { + m_SinkList[inputIndex]->WriteImageInformation(); + } +} + +void +MultiImageFileWriter +::UpdateOutputData(itk::DataObject * itkNotUsed(output)) +{ + /** + * prevent chasing our tail + */ + if (this->m_Updating) + { + return; + } + + /** + * Prepare all the outputs. This may deallocate previous bulk data. + */ + this->PrepareOutputs(); + + this->SetAbortGenerateData(0); + this->SetProgress(0.0); + this->m_Updating = true; + + // Initialize streaming + this->InitializeStreaming(); + + /** + * Tell all Observers that the filter is starting + */ + this->InvokeEvent(itk::StartEvent()); + + this->UpdateProgress(0); + m_CurrentDivision = 0; + m_DivisionProgress = 0; + + /** Loop over the number of pieces, set and propagate requested regions then + * update pipeline upstream + */ + int numInputs = m_SinkList.size(); //this->GetNumberOfInputs(); + m_StreamRegionList.resize(numInputs); + itkDebugMacro( "Number of streaming divisions: " << m_NumberOfDivisions); + + // Add observer only to first input + if(numInputs > 0) + { + m_IsObserving = false; + m_ObserverID = 0; + + typedef itk::MemberCommand<Self> CommandType; + typedef CommandType::Pointer CommandPointerType; + + CommandPointerType command = CommandType::New(); + command->SetCallbackFunction(this, &Self::ObserveSourceFilterProgress); + + itk::ProcessObject* src = this->GetInput(0)->GetSource(); + m_ObserverID = src->AddObserver(itk::ProgressEvent(), command); + m_IsObserving = true; + } + + for (m_CurrentDivision = 0; m_CurrentDivision < m_NumberOfDivisions && !this->GetAbortGenerateData(); + m_CurrentDivision++, m_DivisionProgress = 0, this->UpdateFilterProgress()) + { + + // Update all stream regions + for(int inputIndex = 0; inputIndex < numInputs; ++inputIndex) + { + m_StreamRegionList[inputIndex] = GetStreamRegion(inputIndex); + } + + // NOTE : this reset was probably designed to work with the next section + // Where the final requested region is the "union" between the computed + // requested region and the current requested region. + + // Reset requested regions for all images + for(int inputIndex = 0; inputIndex < numInputs; ++inputIndex) + { + ResetAllRequestedRegions(m_SinkList[inputIndex]->GetInput()); + } + + for(int inputIndex = 0; inputIndex < numInputs; ++inputIndex) + { + + ImageBaseType::Pointer inputPtr = m_SinkList[inputIndex]->GetInput(); // const_cast<ImageBaseType*>(this->GetInput(inputIndex)); + //RegionType streamRegion = GetStreamRegion(inputIndex); + RegionType inputRequestedRegion = m_StreamRegionList[inputIndex]; + + const RegionType & currentInputRequestedRegion = inputPtr->GetRequestedRegion(); + if( currentInputRequestedRegion != inputPtr->GetLargestPossibleRegion() && currentInputRequestedRegion.GetNumberOfPixels() != 0) + { + IndexType startIndex = currentInputRequestedRegion.GetIndex(); + IndexType lastIndex = currentInputRequestedRegion.GetUpperIndex(); + startIndex[0] = std::min(startIndex[0], inputRequestedRegion.GetIndex(0)); + startIndex[1] = std::min(startIndex[1], inputRequestedRegion.GetIndex(1)); + lastIndex[0] = std::max(lastIndex[0], inputRequestedRegion.GetUpperIndex()[0]); + lastIndex[1] = std::max(lastIndex[1], inputRequestedRegion.GetUpperIndex()[1]); + inputRequestedRegion.SetIndex(startIndex); + inputRequestedRegion.SetUpperIndex(lastIndex); + } + + inputPtr->SetRequestedRegion(inputRequestedRegion); + inputPtr->PropagateRequestedRegion(); + } + + for(int inputIndex = 0; inputIndex < numInputs; ++inputIndex) + { + ImageBaseType* inputPtr = m_SinkList[inputIndex]->GetInput(); // const_cast<ImageBaseType*>(this->GetInput(inputIndex)); + //RegionType streamRegion = GetStreamRegion(inputIndex); + inputPtr->UpdateOutputData(); + } + + /** Call GenerateData to write streams to files if needed */ + this->GenerateData(); + + } + + /** + * If we ended due to aborting, push the progress up to 1.0 (since + * it probably didn't end there) + */ + if (!this->GetAbortGenerateData()) + { + this->UpdateProgress(1.0); + } + + // Notify end event observers + this->InvokeEvent(itk::EndEvent()); + + if (m_IsObserving) + { + ImageBaseType::Pointer inputPtr = m_SinkList[0]->GetInput(); // const_cast<ImageBaseType *>(this->GetInput(0)); + itk::ProcessObject* source = inputPtr->GetSource(); + m_IsObserving = false; + source->RemoveObserver(m_ObserverID); + } + + /** + * Release any inputs if marked for release + */ + this->ReleaseInputs(); + + // Mark that we are no longer updating the data in this filter + this->m_Updating = false; + +} + + +void +MultiImageFileWriter +::GenerateInputRequestedRegion() +{ + Superclass::GenerateInputRequestedRegion(); + + int numInputs = m_SinkList.size(); //this->GetNumberOfInputs(); + + for (int i = 0; i < numInputs; ++i) + { + ImageBaseType* inputPtr = m_SinkList[i]->GetInput(); //const_cast<ImageBaseType*>(this->GetInput(i)); + + if(!inputPtr) + { + return; + } + RegionType lregion = inputPtr->GetLargestPossibleRegion(); + SizeType rsize; + rsize.Fill(0); + lregion.SetSize(rsize); + + inputPtr->SetRequestedRegion(lregion); + } +} + +void +MultiImageFileWriter +::GenerateData() +{ + int numInputs = m_SinkList.size(); //this->GetNumberOfInputs(); + + for(int inputIndex = 0; inputIndex < numInputs; ++inputIndex) + { + + //ImageBaseType* inputPtr = const_cast<ImageBaseType*>(this->GetInput(inputIndex)); + //RegionType streamRegion = GetStreamRegion(inputIndex); + + m_SinkList[inputIndex]->Write(m_StreamRegionList[inputIndex]); + } +} + +MultiImageFileWriter::RegionType +MultiImageFileWriter +::GetStreamRegion(int inputIndex) +{ + const SinkBase::Pointer sink = m_SinkList[inputIndex]; + RegionType region = sink->GetInput()->GetLargestPossibleRegion(); + //~ region.SetIndex(1, region.GetIndex(1) + sink->GetTopMarginTrimSize()); + //~ region.SetSize(1, region.GetSize(1) - sink->GetTopMarginTrimSize() - sink->GetBottomMarginTrimSize()); + + // TODO : call splitter + return region; +} + +} // end of namespace otb