From 3fde3d39ac76b8a3bf4703fe956d98dd96c40259 Mon Sep 17 00:00:00 2001
From: Guillaume Pasero <guillaume.pasero@c-s.fr>
Date: Mon, 20 Nov 2017 10:11:56 +0100
Subject: [PATCH] ADD: new class for multi-writer handling (WIP)

---
 .../ImageIO/include/otbMultiImageFileWriter.h | 322 ++++++++++++++
 .../include/otbMultiImageFileWriter.txx       | 263 +++++++++++
 .../ImageIO/src/otbMultiImageFileWriter.cxx   | 421 ++++++++++++++++++
 3 files changed, 1006 insertions(+)
 create mode 100644 Modules/IO/ImageIO/include/otbMultiImageFileWriter.h
 create mode 100644 Modules/IO/ImageIO/include/otbMultiImageFileWriter.txx
 create mode 100644 Modules/IO/ImageIO/src/otbMultiImageFileWriter.cxx

diff --git a/Modules/IO/ImageIO/include/otbMultiImageFileWriter.h b/Modules/IO/ImageIO/include/otbMultiImageFileWriter.h
new file mode 100644
index 0000000000..ca2601b32f
--- /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 0000000000..81417a60ea
--- /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 0000000000..c6fdff5c65
--- /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
-- 
GitLab