From bd044664d54c150eb0a47c5e51f7f932d761fc55 Mon Sep 17 00:00:00 2001
From: Luc Hermitte <luc.hermitte@csgroup.eu>
Date: Mon, 31 Aug 2020 17:14:31 +0200
Subject: [PATCH] ENH: Contribute 2 S1TilingApps to OTB

---
 .../AppImageUtils/app/CMakeLists.txt          |  10 +
 .../AppImageUtils/app/otbClampROI.cxx         | 129 ++++++
 .../AppImageUtils/app/otbSynthetize.cxx       | 134 ++++++
 Modules/Core/Common/include/otbInterval.h     |  99 +++++
 Modules/Core/Common/include/otbLogHelpers.h   |  64 +++
 Modules/Core/Common/include/otbSpan.h         | 251 +++++++++++
 Modules/Core/Common/include/otbZipIterator.h  | 419 ++++++++++++++++++
 .../Functor/include/otbSynthetizeFilter.h     | 239 ++++++++++
 .../include/otbClampROIFilter.h               | 154 +++++++
 .../include/otbClampROIFilter.hxx             | 216 +++++++++
 .../S1TilingSupportApplications.remote.cmake  |   2 +-
 11 files changed, 1716 insertions(+), 1 deletion(-)
 create mode 100644 Modules/Applications/AppImageUtils/app/otbClampROI.cxx
 create mode 100644 Modules/Applications/AppImageUtils/app/otbSynthetize.cxx
 create mode 100644 Modules/Core/Common/include/otbInterval.h
 create mode 100644 Modules/Core/Common/include/otbLogHelpers.h
 create mode 100644 Modules/Core/Common/include/otbSpan.h
 create mode 100644 Modules/Core/Common/include/otbZipIterator.h
 create mode 100644 Modules/Core/Functor/include/otbSynthetizeFilter.h
 create mode 100644 Modules/Filtering/ImageManipulation/include/otbClampROIFilter.h
 create mode 100644 Modules/Filtering/ImageManipulation/include/otbClampROIFilter.hxx

diff --git a/Modules/Applications/AppImageUtils/app/CMakeLists.txt b/Modules/Applications/AppImageUtils/app/CMakeLists.txt
index 8f2981b694..f1b82f4031 100644
--- a/Modules/Applications/AppImageUtils/app/CMakeLists.txt
+++ b/Modules/Applications/AppImageUtils/app/CMakeLists.txt
@@ -92,3 +92,13 @@ OTB_CREATE_APPLICATION(
   NAME           Mosaic
   SOURCES        otbMosaic.cxx
   LINK_LIBRARIES ${${otb-module}_LIBRARIES})
+
+OTB_CREATE_APPLICATION(
+  NAME           ClampROI
+  SOURCES        otbClampROI.cxx
+  LINK_LIBRARIES ${${otb-module}_LIBRARIES})
+
+OTB_CREATE_APPLICATION(
+  NAME           Synthetize
+  SOURCES        otbSynthetize.cxx
+  LINK_LIBRARIES ${${otb-module}_LIBRARIES})
diff --git a/Modules/Applications/AppImageUtils/app/otbClampROI.cxx b/Modules/Applications/AppImageUtils/app/otbClampROI.cxx
new file mode 100644
index 0000000000..0d49ca2433
--- /dev/null
+++ b/Modules/Applications/AppImageUtils/app/otbClampROI.cxx
@@ -0,0 +1,129 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 "otbClampROIFilter.h"
+#include "otbWrapperApplication.h"
+#include "otbWrapperApplicationFactory.h"
+
+namespace otb
+{
+namespace Wrapper
+{
+
+/**
+ * Application that fills margins to 0.
+ *
+ * This application is similar to ExtractROI with the difference the margin is
+ * kept, and filled with 0.
+ *
+ * This application is used to implement the _cut_ processing in S1Tiling
+ * chain.
+ *
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ */
+class ClampROI : public Application
+{
+public:
+  using Self    = ClampROI;
+  using Pointer = itk::SmartPointer<Self>;
+
+  itkNewMacro(Self);
+  itkTypeMacro(ClampROI, otb::Wrapper::Application);
+
+private:
+  void DoInit() override
+  {
+    SetName("ClampROI");
+
+    SetDescription("This is the ClampROI application, version X.X.X");
+    SetDocLongDescription(
+        "This application is similar to ExtractROI in the sense it extracts a Region of Interrest.\n"
+        "However, the region outside of the ROI isn't trimmed, but set to 0.\n"
+        "\n"
+        "The filter set lines of index < threshold.y, and of index >= threshold.y to 0\n"
+        "The filter set columns of index < threshold.x, and of index >= threshold.x to 0");
+
+    SetDocLimitations("This application only works on scalar (and complex) images.");
+    SetDocAuthors("Luc Hermitte (CS Group)");
+    SetDocSeeAlso("ManageNoData, ExtractROI");
+    AddDocTag("otb::Wrapper::Tags::Manip");
+
+    AddParameter(ParameterType_InputImage,  "in",   "Input image");
+    SetParameterDescription("in", "Scalar Input image");
+
+    AddParameter(ParameterType_OutputImage, "out", "Output Image");
+    SetParameterDescription("out", "Scalar Output image");
+
+    AddParameter(ParameterType_Group, "threshold", "threshold group");
+    AddParameter(ParameterType_Group, "threshold.y", "threshold group");
+    MandatoryOff("threshold");
+    MandatoryOff("threshold.y");
+    AddParameter(ParameterType_Int, "threshold.x", "Column index threshold");
+    SetParameterDescription("threshold.x", "Column index threshold");
+    SetDefaultParameterInt("threshold.x", 0);
+
+    AddParameter(ParameterType_Int, "threshold.y.start", "Top line index threshold");
+    SetParameterDescription("threshold.y.start", "Top line index threshold");
+    SetDefaultParameterInt("threshold.y.start", 0);
+
+    AddParameter(ParameterType_Int, "threshold.y.end", "Bottom line index threshold");
+    SetParameterDescription("threshold.y.end", "Bottom line index threshold");
+    SetDefaultParameterInt("threshold.y.end", 0);
+
+    AddRAMParameter();
+  }
+
+  void DoUpdateParameters() override
+  {}
+
+  void DoExecute() override
+  {
+    auto const thrX = GetParameterInt("threshold.x");
+    auto const thrYtop = GetParameterInt("threshold.y.start");
+    auto const thrYbot = GetParameterInt("threshold.y.end");
+    if (thrX < 0)
+      itkExceptionMacro("The column threshold is expected to be positive");
+    if (thrYtop < 0)
+      itkExceptionMacro("The top line threshold is expected to be positive");
+    if (thrYbot < 0)
+      itkExceptionMacro("The bottom line threshold is expected to be positive");
+    if (thrX == 0 && thrYtop == 0 && thrYbot == 0)
+      itkExceptionMacro("Don't use ClampROI to clamp nothing!");
+
+    auto filter = ClampROIFilter<FloatImageType>::New();
+    assert(thrX >= 0);
+    assert(thrYtop >= 0);
+    assert(thrYbot >= 0);
+    filter->SetThresholdX(thrX);
+    filter->SetThresholdYtop(thrYtop);
+    filter->SetThresholdYbot(thrYbot);
+    filter->SetInput(GetParameterFloatImage("in"));
+
+    SetParameterOutputImage("out", filter->GetOutput());
+    RegisterPipeline();
+  }
+
+};
+
+} // otb::Wrapper namespace
+} // otb namespace
+
+OTB_APPLICATION_EXPORT(otb::Wrapper::ClampROI)
diff --git a/Modules/Applications/AppImageUtils/app/otbSynthetize.cxx b/Modules/Applications/AppImageUtils/app/otbSynthetize.cxx
new file mode 100644
index 0000000000..fa77fdcba8
--- /dev/null
+++ b/Modules/Applications/AppImageUtils/app/otbSynthetize.cxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 "otbSynthetizeFilter.h"
+#include "otbWrapperApplication.h"
+#include "otbWrapperApplicationFactory.h"
+#include "otbImageFileReader.h"
+#include <set>
+
+namespace otb
+{
+namespace Wrapper
+{
+
+/**
+ * This application synthetizes/reduces multiple inputs into a single one.
+ * In that particular case, for each output pixel, this application will
+ * consider the corresponding pixels from all the input images, and keep the
+ * first one that isn't equal to 0.
+ *
+ * This application is used to implement the _concatenate_ processing in
+ * S1Tiling chain.
+ *
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \todo find a better name for the application. Alas `otbConcatenate` is
+ * already used...
+ */
+class Synthetize : public Application
+{
+public:
+  using Self    = Synthetize;
+  using Pointer = itk::SmartPointer<Self>;
+
+  itkNewMacro(Self);
+  itkTypeMacro(Synthetize, otb::Wrapper::Application);
+
+private:
+  using ReaderType = otb::ImageFileReader<FloatImageType>;
+
+  void DoInit() override
+  {
+    SetName("Synthetize");
+
+    SetDescription("This is the Synthetize application, version X.X.X");
+    SetDocLongDescription("Concatenate a list of images of the same size into a single single-channel image.\n\
+        It keeps the first non-null pixel value found in the input list.");
+
+    SetDocLimitations("This application will break incoming pipelines.");
+    SetDocAuthors("Luc Hermitte (CS Group)");
+    SetDocSeeAlso("");
+    AddDocTag("otb::Wrapper::Tags::Manip");
+
+    AddParameter(ParameterType_StringList,  "il",  "Input images list");
+    SetParameterDescription("il", "Input image list");
+
+    AddParameter(ParameterType_OutputImage, "out", "Output Image");
+    SetParameterDescription("out","Output image.");
+
+    AddRAMParameter();
+  }
+
+  void DoUpdateParameters() override
+  {}
+
+  void DoExecute() override
+  {
+    // Get the input image list
+    auto inNameList = GetParameterStringList("il");
+
+    // checking the input images list validity
+    auto const nbImages = inNameList.size();
+
+    if (nbImages == 0)
+    {
+      itkExceptionMacro("No input Image set...; please set at least one input image");
+    }
+
+    auto functor = [](auto input) {
+      assert(!input.empty());
+      auto const wh = std::find_if(
+          input.begin(), input.end()-1,
+          [](auto v){ return v != 0;});
+      return *wh;
+    };
+    auto filter = MakeSynthetizeFilter<FloatImageType, FloatImageType>(functor);
+
+    for (unsigned int i = 0; i < nbImages; i++)
+    {
+      // Given the explicit use of a Reader, this application cannot be used in
+      // a in-memory pipeline
+      auto reader = ReaderType::New();
+      // currentImage->SetExtendedFileName(inNameList[i]);
+      reader->SetFileName(inNameList[i]);
+      auto currentImage = reader->GetOutput();
+      currentImage->UpdateOutputInformation();
+
+      otbAppLogINFO(<< "Image #" << i + 1 << " has " << currentImage->GetNumberOfComponentsPerPixel() << " components");
+
+      filter->SetInput(i, currentImage);
+      m_Cache.insert(reader);
+    }
+
+    SetParameterOutputImage("out", filter->GetOutput());
+    RegisterPipeline(); // TODO: check!!
+  }
+
+  // Needed to register the inputs handled manually
+  // and not with a VectorImageList through GetParameterImageList
+  std::set<ReaderType::Pointer> m_Cache;
+};
+
+} // otb::Wrapper namespace
+} // otb namespace
+
+OTB_APPLICATION_EXPORT(otb::Wrapper::Synthetize)
diff --git a/Modules/Core/Common/include/otbInterval.h b/Modules/Core/Common/include/otbInterval.h
new file mode 100644
index 0000000000..5e6ce66b9b
--- /dev/null
+++ b/Modules/Core/Common/include/otbInterval.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbInterval_h
+#define otbInterval_h
+
+#include "itkIntTypes.h"
+#include <cassert>
+#include <ostream>
+
+namespace otb
+{
+/** Simplified index interval.
+ * Inspired by `boost::numeric::interval`.
+ *
+ * \invariant Can be empty.
+ * \invariant lower <= upper
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+class Interval
+{
+public:
+  using IndexType = itk::IndexValueType;
+  using SizeType  = itk::SizeValueType;
+
+  /** Init constructor from the pair of its extremities. */
+  constexpr Interval(IndexType l, IndexType u) noexcept
+    : m_lower(l), m_upper(u)
+    {
+      assert(l <= u);
+    }
+
+  /** Alternate factory function from a position and a length. */
+  static constexpr Interval OfLength(IndexType low, SizeType len) noexcept{
+    return Interval{low, IndexType(low+len)};
+  }
+
+  constexpr SizeType size() const noexcept
+  { return m_upper - m_lower; }
+
+  constexpr bool empty() const noexcept
+  { return m_lower == m_upper; }
+
+  constexpr IndexType lower() const noexcept
+  { return m_lower; }
+
+  constexpr IndexType upper() const noexcept
+  { return m_upper; }
+
+  /** Computes the intersection between two interals.
+   * @return their intersection
+   * @return {0,0} if theyr don't intersect.
+   *
+   * @note this function is an hidden friend
+   */
+  friend constexpr Interval intersect(
+      Interval const& lhs, Interval const& rhs) noexcept
+  {
+    auto const low = std::max(lhs.lower(), rhs.lower());
+    auto const upp = std::min(lhs.upper(), rhs.upper());
+
+    return low <= upp ? Interval{low, upp} : Interval{0,0};
+  }
+
+  /** Stream inserter for intervals.
+   * @note this function is an hidden friend
+   */
+  friend std::ostream & operator<<(std::ostream & os, Interval const& v)
+  {
+    return os << '[' << v.lower() << ".." << v.upper() << '[';
+  }
+
+private:
+  IndexType m_lower;
+  IndexType m_upper;
+};
+} // otb namespace
+
+#endif  // otbInterval_h
diff --git a/Modules/Core/Common/include/otbLogHelpers.h b/Modules/Core/Common/include/otbLogHelpers.h
new file mode 100644
index 0000000000..98239512b3
--- /dev/null
+++ b/Modules/Core/Common/include/otbLogHelpers.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbLogHelpers_h
+#define otbLogHelpers_h
+
+#include "itkImageRegion.h"
+#include <ostream>
+
+namespace otb
+{
+
+/** Helper class to log region in a more human readable way:
+ * e.g.
+ * \code
+ x ∈ [0..42[, y ∈ [12..24[, size=42x12 @(0, 12)
+ * \endcode
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+struct NeatRegionLogger
+{
+  using RegionType = itk::ImageRegion<2u>;
+  NeatRegionLogger(RegionType const& region) : m_region(region) {}
+
+  friend std::ostream & operator<<(std::ostream & os, NeatRegionLogger const& r)
+  {
+    auto const& size = r.m_region.GetSize();
+    auto const& idx  = r.m_region.GetIndex();
+    auto const  idx_x1  = idx[0];
+    int  const  idx_x2  = idx[0] + size[0];
+    int  const  idx_y1  = idx[1];
+    auto const  idx_y2  = idx[1] + size[1];
+    os
+      << "x ∈ ["<<idx_x1 << ".." << idx_x2 << '['
+      << ", y ∈ ["<<idx_y1 << ".." << idx_y2 << '['
+      << ", size=" << size[0]<<'x'<<size[1] << " @("<<idx_x1<<", "<<idx_y1<<')'
+      ;
+    return os ;
+  }
+  RegionType const& m_region;
+};
+} // otb namespace
+
+#endif  // otbLogHelpers_h
diff --git a/Modules/Core/Common/include/otbSpan.h b/Modules/Core/Common/include/otbSpan.h
new file mode 100644
index 0000000000..0b39f86736
--- /dev/null
+++ b/Modules/Core/Common/include/otbSpan.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 Span_h
+#define Span_h
+
+#include <type_traits>
+#include <iterator>
+#include <cassert>
+
+namespace otb
+{
+
+/** Span class inspired by C++20 standard.
+ * \todo fix RW / RO interface
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename T> struct Span
+{
+  /**\name Typedefs */
+  //@{
+  using element_type           = T;
+  using value_type             = std::remove_cv_t<T>;
+  using index_type             = std::size_t;
+  using difference_type        = std::ptrdiff_t;
+  using pointer                = T*;
+  using const_pointer          = T const*;
+  using reference              = T&;
+  using const_reference        = T const&;
+  using iterator               = T*;
+  using const_iterator         = T const*;
+  using reverse_iterator       = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  //@}
+
+  /**\name Constructors */
+  //@{
+  constexpr Span() noexcept = default;
+  constexpr Span(pointer ptr, index_type count) noexcept
+    : m_buffer(ptr), m_size(count)
+    {
+      assert(! (!ptr) xor (!count));
+    }
+  constexpr Span(pointer first, pointer last) noexcept
+    : Span(first, last - first)
+    {
+      assert(! (!first) xor (!last));
+      assert(first <= last);
+    }
+  template <std::size_t N> constexpr Span(element_type (&arr)[N]) noexcept
+    : Span(arr, N)
+    {}
+
+  /** Converting constructor from a contiguous container.
+   * \pre The Container shall be contiguous
+   * \warning The lifetime of the span shall not exceed the one of the container.
+   * Be sure to not store the span locally, and initialize it from a rvalue.
+   * The use case where a span is initialize from a rvalue shall be restricted
+   * to function parameters.
+   * \code
+   * std::vector<T> f();
+   * void g(Span<T> sp);
+   * ...
+   * Span<T> sp(f()); // NO!!! Use after release
+   * g(f());          // OK!!
+   * \endcode
+   *
+   * \todo static_assert the container is contiguous
+   */
+  template <class Container> constexpr Span(Container&& cont) noexcept
+    : Span(&cont[0], cont.size())
+    {
+      // We cannot use op[] which has an assertion sometimes.
+      // assert(&const[size()] == (&cont[0] + size()));
+    }
+  template <class U> constexpr Span(const otb::Span<U>& s) noexcept
+    : Span(s.data(), s.size())
+    {}
+  constexpr Span(const Span& other) noexcept = default;
+  //@}
+
+  /// shallow assignment
+  Span& operator=(Span const&) noexcept = default;
+
+  /// No-op destructor
+  ~Span() = default;
+
+  /**\name Iterators */
+  //@{
+  constexpr iterator       begin ()       noexcept { return data(); }
+  constexpr iterator       end   ()       noexcept { return data()+size(); }
+  constexpr const_iterator begin () const noexcept { return data(); }
+  constexpr const_iterator end   () const noexcept { return data()+size(); }
+  constexpr const_iterator cbegin() const noexcept { return data(); }
+  constexpr const_iterator cend  () const noexcept { return data()+size(); }
+
+  constexpr reverse_iterator       rbegin ()       noexcept { return reverse_iterator(end()); }
+  constexpr reverse_iterator       rend   ()       noexcept { return reverse_iterator(begin()); }
+  constexpr const_reverse_iterator crbegin() const noexcept { return reverse_const_iterator(cend()); }
+  constexpr const_reverse_iterator crend  () const noexcept { return reverse_const_iterator(cbegin()); }
+  //@}
+
+  /**\name Element access */
+  //@{
+  constexpr pointer         data ()       noexcept { return m_buffer; }
+  constexpr const_pointer   data () const noexcept { return m_buffer; }
+  constexpr reference       front()       noexcept { assert(!empty()); return *data(); }
+  constexpr const_reference front() const noexcept { assert(!empty()); return *data(); }
+  constexpr reference       back ()       noexcept { assert(!empty()); return *data()+size()-1; }
+  constexpr const_reference back () const noexcept { assert(!empty()); return *data()+size()-1; }
+
+  constexpr reference       operator[](index_type p)       noexcept
+  {
+    assert(p < size());
+    return data()[p];
+  }
+  constexpr const_reference operator[](index_type p) const noexcept
+  {
+    assert(p < size());
+    return data()[p];
+  }
+  //@}
+
+  /**\name Observers */
+  //@{
+  constexpr index_type size () const noexcept { return m_size; }
+  constexpr bool       empty() const noexcept { return size() == 0; }
+  //@}
+
+  /**\name Subviews */
+  //@{
+  constexpr Span first(index_type n) const noexcept
+  { assert(n < size()); return Span(data(), n);}
+  constexpr Span last(index_type n) const noexcept
+  { assert(n < size()); return Span(data()-n, n);}
+  //@}
+
+private:
+  pointer    m_buffer = nullptr;
+  index_type m_size   = 0;
+};
+
+/**
+ * Helper function to make a span from a range defined with pointers.
+ * Compensate the fact we are not have access to C++17 `span{ptr1, ptr2}`.
+ * \tparam T  Auto deduced type of the elements
+ * \param[in] first  start of the memory zone
+ * \param[in] last   end of the memory zone
+ * \return a span over `[first, last)`
+ * \throw None
+ * \pre `NOT first != nullptr XOR last != nullptr`
+ * \pre `first < last`
+ * \pre `[first, last)` can be iterated
+ * \see `otb::Span<>`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename T>
+inline
+auto make_span(T* first, T* last) noexcept
+{
+  return Span<T>(first, last);
+}
+
+/**
+ * Helper function to make a span from a range defined with a pointer plus a
+ * size.
+ * Compensate the fact we are not have access to C++17 `span{ptr, count}`.
+ * \tparam T  Auto deduced type of the elements
+ * \param[in] first  start of the memory zone
+ * \param[in] count  number of elements in the span.
+ * \return a span over `[first, first+count)`
+ * \throw None
+ * \pre `NOT first != nullptr XOR count != 0`
+ * \see `otb::Span<>`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename T>
+inline
+auto make_span(T* first, std::size_t count) noexcept
+{
+  return Span<T>(first, count);
+}
+
+/**
+ * Helper function to make a span from a static array.
+ * Compensate the fact we are not have access to C++17 `span{array}`.
+ * \tparam T  Auto deduced type of the elements
+ * \tparam N  Auto deduced number of elements in the array
+ * \param[in] array  static array
+ * \return a span over `[&array[0], &array[N])`
+ * \throw None
+ * \see `otb::Span<>`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename T, std::size_t N>
+inline
+auto make_span(T (&arr)[N]) noexcept
+{
+  return Span<T>(arr);
+}
+
+/**
+ * Helper function to make a span from a contiguous container.
+ * Compensate the fact we are not have access to C++17 `span{container}`.
+ * \tparam ContiguousContainer  Auto deduced type of the container
+ * \param[in] cont  container of contiguous elements
+ * \return a span over `[c.data(), c.size())`
+ * \throw None
+ * \see `otb::Span<>`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename ContiguousContainer>
+inline
+auto make_span(ContiguousContainer & c) noexcept
+{
+  return Span<decltype(*c.data())>(c);
+}
+
+}
+// otb namespace
+
+
+#endif // Span_h
diff --git a/Modules/Core/Common/include/otbZipIterator.h b/Modules/Core/Common/include/otbZipIterator.h
new file mode 100644
index 0000000000..f95635a16a
--- /dev/null
+++ b/Modules/Core/Common/include/otbZipIterator.h
@@ -0,0 +1,419 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbZipIterator_h
+#define otbZipIterator_h
+
+#include "otbSpan.h"
+#include "itkMacro.h"
+#include <type_traits>
+#include <vector>
+#include <cassert>
+
+namespace otb
+{
+namespace internals
+{
+
+struct ConstTag {};
+struct MutableTag {};
+
+/**
+ * Wrapper to present list of iterators as a single iterator.
+ *
+ * \invariant This class shall not be inherited.
+ * \invariant All sub iterators are always synchronised (same region, size...)
+ * \invariant `!m_iterators.empty()`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ */
+template <typename TImageIterator, typename ConstOrMutable>
+class ZipIterator
+{
+public:
+  /**\name ITK Constants and Typedefs */
+  //@{
+  /** Iterator type for zipped iterators. */
+  using ImageIteratorType     = TImageIterator;
+
+  /** Image type alias support */
+  using ImageType             = typename TImageIterator::ImageType;
+
+  /** Dimension of the image the iterator walks.  This constant is needed so
+   * functions that are templated over image iterator type (as opposed to
+   * being templated over pixel type and dimension) can have compile time
+   * access to the dimension of the image that the iterator walks. */
+  static constexpr unsigned int ImageIteratorDimension = ImageIteratorType::ImageIteratorDimension;
+
+  using Self                  = ZipIterator;
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacroNoParent(ZipIterator);
+
+  /** Index type alias support */
+  using IndexType             = typename ImageIteratorType::IndexType;
+
+  /** Size type alias support */
+  using SizeType              = typename ImageIteratorType::SizeType;
+
+  /** Offset type alias support */
+  using OffsetType            = typename ImageIteratorType::OffsetType;
+
+  /** Region type alias support */
+  using RegionType            = typename ImageIteratorType::RegionType;
+
+  /** PixelContainer type alias support. Used to refer to the container for
+   * the pixel data. While this was already typdef'ed in the superclass
+   * it needs to be redone here for this subclass to compile properly with gcc. */
+  using PixelContainer        = typename ImageIteratorType::PixelContainer;
+  using PixelContainerPointer = typename PixelContainer::Pointer;
+
+  /** Internal Pixel Type */
+  using InternalPixelType     = typename ImageIteratorType::InternalPixelType;
+
+  /** External Pixel Type */
+  using PixelType             = typename ImageIteratorType::PixelType;
+
+  /**  Accessor type that convert data between internal and external
+   *  representations. */
+  using AccessorType          = typename ImageIteratorType::AccessorType;
+  using AccessorFunctorType   = typename ImageIteratorType::AccessorFunctorType;
+  //@}
+
+  /**\name Constructions & Destruction
+   *
+   * This class follows the rule of 0/5
+   */
+  //@{
+  ZipIterator           ()                   = default;
+  ~ZipIterator          ()                   = default;
+  ZipIterator           (ZipIterator const&) = default;
+  ZipIterator           (ZipIterator     &&) = default;
+  ZipIterator& operator=(ZipIterator const&) = default;
+  ZipIterator& operator=(ZipIterator     &&) = default;
+
+  /** Convertion constructor.
+   * Converts from mutable to const iterator
+   * \see https://quuxplusone.github.io/blog/2018/12/01/const-iterator-antipatterns/
+   */
+  template <bool IsConst_ = std::is_same<ConstOrMutable, ConstTag>::value, class = std::enable_if<IsConst_>>
+  ZipIterator(ZipIterator<ImageIteratorType, MutableTag> const& rhs)
+    : m_iterators(rhs.m_iterators())
+    {}
+
+  /** Convertion move constructor.
+   * Move converts from mutable to const iterator
+   * \see https://quuxplusone.github.io/blog/2018/12/01/const-iterator-antipatterns/
+   */
+  template <bool IsConst_ = std::is_same<ConstOrMutable, ConstTag>::value, class = std::enable_if<IsConst_>>
+  ZipIterator(ZipIterator<ImageIteratorType, MutableTag> && rhs)
+    : m_iterators(move(rhs.m_iterators()))
+    {}
+
+  /**
+   * Init Constructor.
+   * Constructs a `ZipIterator` from a list of Images and a Region.
+   * \param[in,out] images  List of images
+   * \param[in]     region  Region to iterate over
+   * \pre There should be at least one image
+   */
+  ZipIterator(Span<ImageType * const> images, RegionType const& region)
+  {
+    assert(! images.empty());
+    m_iterators.reserve(images.size());
+
+    for (auto & im: images)
+      m_iterators.emplace_back(im, region);
+  }
+
+  // static_assert(std::is_copy_constructible<Self>::value, "Requires copy construction");
+  // static_assert(std::is_trivially_copy_constructible<Self>::value, "Requires trivial copy construction");
+  //@}
+
+  /**\name Comparison */
+  //@{
+  friend bool operator==(ZipIterator const& lhs, ZipIterator const& rhs)
+  {
+    assert(!lhs.m_iterators.empty());
+    assert(lhs.m_iterators.size() == rhs.m_iterators.size());
+
+    return lhs.m_iterators.front() == rhs.m_iterators.front();
+  }
+  friend bool operator!=(ZipIterator const& lhs, ZipIterator const& rhs)
+  { return ! (lhs == rhs); }
+
+  friend bool operator<=(ZipIterator const& lhs, ZipIterator const& rhs)
+  {
+    assert(!lhs.m_iterators.empty());
+    assert(lhs.m_iterators.size() == rhs.m_iterators.size());
+
+    return lhs.m_iterators.front() <= rhs.m_iterators.front();
+  }
+  friend bool operator<(ZipIterator const& lhs, ZipIterator const& rhs)
+  {
+    assert(!lhs.m_iterators.empty());
+    assert(lhs.m_iterators.size() == rhs.m_iterators.size());
+
+    return lhs.m_iterators.front() < rhs.m_iterators.front();
+  }
+  friend bool operator>=(ZipIterator const& lhs, ZipIterator const& rhs)
+  { return ! (lhs < rhs); }
+  friend bool operator>(ZipIterator const& lhs, ZipIterator const& rhs)
+  { return ! (lhs <= rhs); }
+  //@}
+
+  /**\name ITK iterator interface */
+  //@{
+  // What about GetIndex() and SetIndex ?
+  /** Fetch the region iterated by the iterator. */
+  auto const& GetRegion() const
+  {
+    assert(!m_iterators.empty());
+    return m_iterators.front().GetRegion();
+  }
+
+  /** Set the region iterated by the iterator. */
+  void SetRegion(RegionType const& region)
+  {
+    for (auto & it : m_iterators)
+      it.SetRegion(region);
+  }
+
+  /** Moves the iterator to the begin of the region iterated. */
+  Self& GoToBegin() {
+    for (auto & it : m_iterators)
+      it.GoToBegin();
+    return *this;
+  }
+  /** Moves the iterator to the end of the region iterated. */
+  Self& GoToEnd() {
+    for (auto & it : m_iterators)
+      it.GoToEnd();
+    return *this;
+  }
+
+  /** Tells whether the iterator is at the begin of the region iterated. */
+  bool IsAtBegin() const {
+    assert(!m_iterators.empty());
+    return m_iterators.front().IsAtBegin();
+  }
+
+  /** Tells the iterator is at the end of the region iterated. */
+  bool IsAtEnd() const {
+    assert(!m_iterators.empty());
+    return m_iterators.front().IsAtEnd();
+  }
+
+  /** Pre-increment the iterator.
+   * As post-increment is less efficient, it hasn't been provided.
+   */
+  Self& operator++() {
+    assert(!IsAtEnd());
+    for (auto & it : m_iterators)
+      ++it;
+    return *this;
+  }
+  /** Removed post-increment operator.
+   * Please use the  preincrement operator!
+   */
+  Self operator++(int) = delete;
+
+  /** Internal typedef to the type holding the list of ITK iterators. */
+  using ImageIteratorList_t = std::vector<ImageIteratorType>;
+  /** Internal Pixel Proxy Type returned by `ZipIterator`.
+   * \author Luc Hermitte (CS Group)
+   * \copyright CNES
+   * \ingroup OTBCommon
+   */
+  struct PixelListProxy
+  {
+    /** Init constructor.
+     * @param[in] iterators List of ITK iterators
+     */
+    explicit PixelListProxy(ImageIteratorList_t const& iterators) noexcept
+      : m_iterators(iterators)
+      {}
+    bool empty()   const noexcept {return m_iterators.empty();}
+    auto size()    const noexcept {return m_iterators.size();}
+
+    /** Internal C++ iterator over the components of the Pixel Proxy.
+     * \author Luc Hermitte (CS Group)
+     * \copyright CNES
+     * \ingroup OTBCommon
+     */
+    struct iterator__
+    {
+      using difference_type   = typename ImageIteratorList_t::difference_type;
+      using value_type        = decltype(typename ImageIteratorList_t::const_iterator{}->Get());
+      using pointer           = value_type*;
+      using reference         = value_type&;
+      using iterator_category = std::forward_iterator_tag;
+
+      explicit iterator__(typename ImageIteratorList_t::const_iterator ref)
+        : reference_to_value(ref){}
+      friend bool operator==(iterator__ const& lhs, iterator__ const& rhs) noexcept
+      { return lhs.reference_to_value == rhs.reference_to_value;}
+      friend bool operator!=(iterator__ const& lhs, iterator__ const& rhs) noexcept
+      { return ! (lhs == rhs);}
+      iterator__ & operator++() noexcept {
+        ++reference_to_value;
+        return *this;
+      }
+      iterator__ & operator--() noexcept {
+        --reference_to_value;
+        return *this;
+      }
+      iterator__ operator+(std::ptrdiff_t offset) const noexcept{
+        return iterator__{reference_to_value + offset};
+      }
+      iterator__ operator-(std::ptrdiff_t offset) const noexcept{
+        return iterator__{reference_to_value - offset};
+      }
+      decltype(auto) operator*() const {
+        return reference_to_value->Get();
+      }
+
+    private:
+      typename ImageIteratorList_t::const_iterator reference_to_value;
+    };
+
+    auto begin()         noexcept { return iterator__(m_iterators.begin()); }
+    auto end()           noexcept { return iterator__{m_iterators.end()}; }
+    auto begin()   const noexcept { return iterator__{m_iterators.begin()}; }
+    auto end()     const noexcept { return iterator__{m_iterators.end()}; }
+    auto cbegin()  const noexcept { return iterator__{m_iterators.cbegin()}; }
+    auto cend()    const noexcept { return iterator__{m_iterators.cend()}; }
+
+    decltype(auto) operator[](std::size_t idx) const {
+      assert(idx < size());
+      return m_iterators[idx].Get();
+    }
+    decltype(auto) operator[](std::size_t idx)       {
+      assert(idx < size());
+      return m_iterators[idx].Get();
+    }
+
+    decltype(auto) front() const {
+      assert(!empty());
+      return m_iterators.front().Get();
+    }
+    decltype(auto) front()       {
+      assert(!empty());
+      return m_iterators.front().Get();
+    }
+    decltype(auto) back() const {
+      assert(!empty());
+      return m_iterators.back().Get();
+    }
+    decltype(auto) back()       {
+      assert(!empty());
+      return m_iterators.back().Get();
+    }
+  private:
+    ImageIteratorList_t const& m_iterators;
+  };
+
+  /** Fetches the value of the current pixel.
+   * \return an iterable proxy over the pixel made of all images.
+   */
+  PixelListProxy Get() const {
+    return PixelListProxy{m_iterators};
+  }
+
+  //@}
+
+  /**\name Mutable Iterator Interface */
+  //@{
+  template <typename MultiCompPixelType>
+  void Set(MultiCompPixelType const& p)
+  {
+    assert(p.size() == m_iterators.size());
+    for (std::size_t i = 0; i!=m_iterators.size(); ++i)
+    {
+      m_iterators[i].Set(p[i]);
+    }
+  }
+  // PixelType & Value(); -- cannot be defined and still preserve direct access
+  // to memory => we don't provide it.
+
+  // ImageType * GetImages();
+  //@}
+
+  /**\name ScanLine Iterator Interface */
+  //@{
+  /** Moves iterator to next line. */
+  Self& NextLine() {
+    for (auto & it : m_iterators)
+      it.NextLine();
+    return *this;
+  }
+  /** Moves iterator to the beginning of the current line. */
+  Self& GoToBeginOfLine() {
+    for (auto & it : m_iterators)
+      it.GoToBeginOfLine();
+    return *this;
+  }
+  /** Moves iterator to the end of the current line. */
+  Self& GoToEndOfLine() {
+    for (auto & it : m_iterators)
+      it.GoToEndOfLine();
+    return *this;
+  }
+  /** Tells whether the iterator is a the end of a line.
+   * \pre `!m_iterators.empty()`
+   */
+  bool IsAtEndOfLine() const {
+    assert(!m_iterators.empty());
+    // Const qualifier has been added to ScanLineIterator::IsAtEndOfLine in ITK
+    // 5.1 => Use const_cast in the mean time...
+    return const_cast<typename ImageIteratorList_t::value_type &>(m_iterators.front()).IsAtEndOfLine();
+  }
+  //@}
+
+private:
+  ImageIteratorList_t m_iterators;
+};
+
+} // otb::internal namespace
+
+/** Typedef for a ZipIterator.
+ * ZipIterator presents a single iterator over a list of image iterators.
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ * \see `otb::internals::ZipIterator<>`
+ */
+template <typename TImageIterator>
+using ZipIterator      = internals::ZipIterator<TImageIterator, internals::MutableTag>;
+/** Typedef for a ZipConstIterator.
+ * ZipConstIterator presents a single iterator over a list of const image iterators.
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \ingroup OTBCommon
+ * \see `otb::internals::ZipIterator<>`
+ */
+template <typename TImageIterator>
+using ZipConstIterator = internals::ZipIterator<TImageIterator, internals::ConstTag>;
+
+} // otb namespace
+
+#endif  // otbZipIterator_h
diff --git a/Modules/Core/Functor/include/otbSynthetizeFilter.h b/Modules/Core/Functor/include/otbSynthetizeFilter.h
new file mode 100644
index 0000000000..e86b3780a1
--- /dev/null
+++ b/Modules/Core/Functor/include/otbSynthetizeFilter.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbSynthetizeFilter_h
+#define otbSynthetizeFilter_h
+
+#include "otbZipIterator.h"
+#include "itkImageToImageFilter.h"
+#include "itkImageScanlineConstIterator.h"
+#include "itkImageScanlineIterator.h"
+#include "itkProgressReporter.h"
+
+namespace otb
+{
+/**
+ * Filter that reduces/synthetize multiple input into a single output.
+ *
+ * This filter makes sure to avoid VectorImages. Instead it works on a
+ * collection of scalar images.
+ * \tparam TInputImage   Type of the input images
+ * \tparam TOutputImage  Type of the output image
+ * \tparam TFunctor      Type of the functor, meant to be auto-deduced by
+ * `MakeSynthetizeFilter()`
+ *
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ * \see for instance `otb::Wrapper::Synthetize`
+ */
+template <typename TInputImage, typename TOutputImage, typename TFunctor>
+class SynthetizeFilter : public itk::ImageToImageFilter<TInputImage, TOutputImage>
+{
+public:
+
+  /**\name Convenient typedefs for simplifying declarations */
+  //@{
+  using InputImageType  = TInputImage;
+  using OutputImageType = TOutputImage;
+  using FunctorType     = TFunctor;
+  //@}
+
+  /**\name Extract dimension from input and output images */
+  //@{
+  itkStaticConstMacro(InputImageDimension,  unsigned int, InputImageType::ImageDimension);
+  itkStaticConstMacro(OutputImageDimension, unsigned int, OutputImageType::ImageDimension);
+  //@}
+  /**\name Standard class typedefs */
+  //@{
+  using Self            = SynthetizeFilter;
+  using Superclass      = itk::ImageToImageFilter<InputImageType, OutputImageType>;
+  using Pointer         = itk::SmartPointer<Self>;
+  using ConstPointer    = itk::SmartPointer<const Self>;
+  //@}
+
+  /** Method for creation through the object factory. */
+  static Pointer New(FunctorType functor)
+  {
+    Pointer smartPtr = new Self(std::move(functor));
+    smartPtr->UnRegister();
+    return smartPtr;
+  }
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(SynthetizeFilter, ImageToImageFilter);
+
+  /**\name Image typedef support */
+  //@{
+  using InputPixelType        = typename InputImageType::PixelType;
+  using OutputPixelType       = typename OutputImageType::PixelType;
+  using InputRealType         = typename itk::NumericTraits<InputPixelType>::RealType;
+  using InputImageRegionType  = typename InputImageType::RegionType;
+  using OutputImageRegionType = typename OutputImageType::RegionType;
+  using InputSizeType         = typename InputImageType::SizeType;
+  using OutputIndexType       = typename OutputImageType::IndexType;
+  using OutputSizeType        = typename OutputImageType::SizeType;
+
+  static_assert(InputImageDimension == OutputImageDimension, "Images have the same number of components");
+
+  using DataObjectPointerArraySizeType = itk::ProcessObject::DataObjectPointerArraySizeType;
+   //@}
+
+  /**\name Access to N-th Input
+   *
+   * Set/Get the nth filter input with or without a specified associated
+   * variable name.
+   */
+  //@{
+  using Superclass::SetNthInput;
+  using Superclass::GetInput;
+
+  /** Return a pointer on the nth filter input */
+  InputImageType* GetNthInput(DataObjectPointerArraySizeType idx)
+  {
+    return const_cast<InputImageType*>(this->GetInput(idx));
+  }
+
+  /** Returns a vector of input images. */
+  std::vector<InputImageType const*> GetInputs() const
+  {
+    std::vector<InputImageType const*> res;
+    auto const  nbInputImages = this->GetNumberOfInputs();
+    res.reserve(nbInputImages);
+    for (std::size_t i = 0 ; i != nbInputImages ; ++i)
+      res.push_back(this->GetInput(i));
+    return res;
+  }
+  //@}
+
+protected:
+  /** Init constructor. */
+  explicit SynthetizeFilter(FunctorType functor)
+    : m_functor(functor){}
+  ~SynthetizeFilter() = default;
+
+  /** Overrides `GenerateOutputInformation` to check images consistency. */
+  void GenerateOutputInformation() override
+  {
+    Superclass::GenerateOutputInformation();
+    CheckInputImageDimensions();
+  }
+
+  // void GenerateInputRequestedRegion() override;
+  // +-> TODO: detect neighborhood to apply pad radius
+
+  /**
+   * Main computation function called by each thread.
+   * \param[in] outputRegionForThread  Specified output region to compute
+   * \param[in] threadId               Id of the computing threads
+   */
+  void ThreadedGenerateData(
+      OutputImageRegionType const& outputRegionForThread,
+      itk::ThreadIdType            threadId) override
+  {
+    using ImageScanlineConstIteratorType = itk::ImageScanlineConstIterator<InputImageType const>;
+    using OutImageScanlineConstIteratorType = itk::ImageScanlineIterator<OutputImageType>;
+    using OutputIterator  = itk::ImageScanlineIterator<OutputImageType>;
+    using InputIterator   = ZipConstIterator<ImageScanlineConstIteratorType>;
+
+    auto const regSizeY = outputRegionForThread.GetSize()[1];
+    itk::ProgressReporter progress( this, threadId, outputRegionForThread.GetNumberOfPixels() / regSizeY );
+
+    InputIterator  inputIterator(this->GetInputs(), outputRegionForThread);
+    OutputIterator outputIterator(this->GetOutput(), outputRegionForThread);
+
+    inputIterator.GoToBegin();
+    outputIterator.GoToBegin();
+    for (
+        ; !inputIterator.IsAtEnd()
+        ; inputIterator.NextLine(), outputIterator.NextLine())
+    {
+      assert(! outputIterator.IsAtEnd());
+      // inputIterator.GoToBeginOfLine();
+      // outputIterator.GoToBeginOfLine();
+      for (
+          ; !inputIterator.IsAtEndOfLine()
+          ; ++inputIterator, ++outputIterator)
+      {
+        assert(!outputIterator.IsAtEndOfLine());
+
+        outputIterator.Set(m_functor(inputIterator.Get()));
+      }
+      progress.CompletedPixel(); // Completed...Line()
+    }
+  }
+
+private:
+
+  void CheckInputImageDimensions()
+  {
+    // Check if input image dimensions match
+    auto const  nbInputImages = this->GetNumberOfInputs();
+    auto const& inputSize = this->GetInput(0)->GetLargestPossibleRegion().GetSize();
+
+    for (auto p = 1U; p < nbInputImages; ++p)
+    {
+      auto const& regionSize = this->GetInput(p)->GetLargestPossibleRegion().GetSize();
+      if (inputSize != regionSize)
+      {
+        itkExceptionMacro(<< "Input images must have the same dimensions.\n"
+            << "band #1 is [" << inputSize[0] << ";" << inputSize[1] << "]\n"
+            << "band #" << p + 1 << " is [" << this->GetInput(p)->GetLargestPossibleRegion().GetSize(0) << ";"
+            << regionSize << "]");
+      }
+    }
+  }
+
+  /** Internal functor.
+   * Can only be set at filter creation through call to `New`.
+   */
+  FunctorType m_functor;
+
+};
+
+/**
+ * Factory function for `SynthetizeFilter`.
+ * `SynthetizeFilter` objects are best made thanks to this factory function
+ * that'll automatically deduce the type of the functor parameter.
+ *
+ * Actually, there is no other to create a `SynthetizeFilter<>` that'll call a
+ * lambda on each pixel.
+ * \tparam TInputImage  Type of the input images
+ * \tparam TOutputImage Type of the output image
+ * \tparam TFunctor     Type of the functor
+ * \param[in] functor   The functor
+ *
+ * \return a new `SynthetizeFilter` object.
+ * \sa `SynthetizeFilter`
+ */
+template <typename TInputImage, typename TOutputImage, typename TFunctor>
+auto MakeSynthetizeFilter(TFunctor functor)
+{
+  auto filter = SynthetizeFilter<TInputImage, TOutputImage, TFunctor>::New(std::move(functor));
+  return filter;
+}
+
+} // otb namespace
+
+#ifndef OTB_MANUAL_INSTANTIATION
+// #include "otbSynthetizeFilter.hxx"
+#endif
+
+#endif  // otbSynthetizeFilter_h
diff --git a/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.h b/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.h
new file mode 100644
index 0000000000..e62e795a2d
--- /dev/null
+++ b/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbClampROIFilter_h
+#define otbClampROIFilter_h
+
+#include "itkImageToImageFilter.h"
+
+namespace otb
+{
+/**
+ * Region clamping filter.
+ * This filter is a kind of ROI pass filter. Data within the ROI is kept with
+ * its original value. Data outside ROI is forced to 0.
+ *
+ * Also, this filter propagate the exact ROI upstream in the pipeline. This
+ * way, if it's piped after another filter, the upstream filter isn't executed
+ * on the data outside the ROI.
+ *
+ * \tparam TImage  Image type.
+ * \sa `otb::ExtractROI<>`
+ * \author Luc Hermitte (CS Group)
+ * \copyright CNES
+ */
+template <typename TImage>
+class ClampROIFilter : public itk::ImageToImageFilter<TImage, TImage>
+{
+public:
+
+  /**\name Convenient typedefs for simplifying declarations */
+  //@{
+  using InputImageType  = TImage;
+  using OutputImageType = TImage;
+  //@}
+
+  /**\name Extract dimension from input and output images */
+  //@{
+  itkStaticConstMacro(InputImageDimension, unsigned int, InputImageType::ImageDimension);
+  itkStaticConstMacro(OutputImageDimension, unsigned int, OutputImageType::ImageDimension);
+  //@}
+  /**\name Standard class typedefs */
+  //@{
+  using Self            = ClampROIFilter;
+  using Superclass      = itk::ImageToImageFilter<InputImageType, OutputImageType>;
+  using Pointer         = itk::SmartPointer<Self>;
+  using ConstPointer    = itk::SmartPointer<const Self>;
+  //@}
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(ClampROIFilter, unused);
+
+  /**\name Image typedef support */
+  //@{
+  using InputPixelType        = typename InputImageType::PixelType;
+  using OutputPixelType       = typename OutputImageType::PixelType;
+  using InputRealType         = typename itk::NumericTraits<InputPixelType>::RealType;
+  using InputImageRegionType  = typename InputImageType::RegionType;
+  using OutputImageRegionType = typename OutputImageType::RegionType;
+  using InputIndexType        = typename InputImageType::IndexType;
+  using InputSizeType         = typename InputImageType::SizeType;
+  using OutputIndexType       = typename OutputImageType::IndexType;
+  using OutputSizeType        = typename OutputImageType::SizeType;
+
+  static_assert(InputImageDimension == OutputImageDimension, "Images have the same number of components");
+   //@}
+
+  /** Column threshold setter. */
+  void SetThresholdX(long threshold) noexcept
+  { m_thresholdX = threshold; }
+  /** Column threshold getter. */
+  long GetThresholdX() const noexcept
+  { return m_thresholdX;}
+
+  /** Top line threshold setter. */
+  void SetThresholdYtop(long threshold) noexcept
+  { m_thresholdYtop = threshold; }
+  /** Top line threshold getter. */
+  long GetThresholdYtop() const noexcept
+  { return m_thresholdYtop;}
+
+  /** Bottom line threshold setter. */
+  void SetThresholdYbot(long threshold) noexcept
+  { m_thresholdYbot = threshold; }
+  /** Bottom line threshold getter. */
+  long GetThresholdYbot() const noexcept
+  { return m_thresholdYbot;}
+
+protected:
+  /// Hidden constructor
+  ClampROIFilter() = default;
+
+  InputImageType      * GetInputImage()       { return const_cast<InputImageType*>(this->GetInput()); }
+  InputImageType const* GetInputImage() const { return this->GetInput(); }
+
+  /** otbClampROIFilter doesn't need an input requested region as large as the
+   * output requested region.
+   * \sa ImageToImageFilter::GenerateInputRequestedRegion()
+   */
+  void CallCopyOutputRegionToInputRegion(
+      InputImageRegionType       & destRegion,
+      OutputImageRegionType const& srcRegion) override
+  {
+    destRegion = OutputRegionToInputRegion(srcRegion);
+  }
+
+  /**
+   * Functional implementation of `CallCopyOutputRegionToInputRegion()`.
+   */
+  InputImageRegionType OutputRegionToInputRegion(
+      OutputImageRegionType const& srcRegion);
+
+  /**
+   * Main computation function called by each thread.
+   * \param[in] outputRegionForThread  Specified output region to compute
+   * \param[in] threadId               Id of the computing threads
+   */
+  void ThreadedGenerateData(
+      OutputImageRegionType const& outputRegionForThread,
+      itk::ThreadIdType            threadId) override;
+
+private:
+  long m_thresholdX    = 0;
+  long m_thresholdYtop = 0;
+  long m_thresholdYbot = 0;
+};
+
+} // otb namespace
+
+#ifndef OTB_MANUAL_INSTANTIATION
+#include "otbClampROIFilter.hxx"
+#endif
+
+#endif  // otbClampROIFilter_h
diff --git a/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.hxx b/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.hxx
new file mode 100644
index 0000000000..fa40583c8c
--- /dev/null
+++ b/Modules/Filtering/ImageManipulation/include/otbClampROIFilter.hxx
@@ -0,0 +1,216 @@
+/*
+ * Copyright(C) 2005-2020 Centre National d'Etudes Spatiales(CNES)
+ *
+ * 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 otbClampROIFilter_hxx
+#define otbClampROIFilter_hxx
+
+#include "otbClampROIFilter.h"
+#include "otbInterval.h"
+#include "otbMacro.h"
+#include "otbLogHelpers.h"
+#include "itkImageScanlineConstIterator.h"
+#include "itkImageScanlineIterator.h"
+#include "itkProgressReporter.h"
+#include <boost/numeric/interval.hpp>
+#include <algorithm>
+#include <cassert>
+#include <ostream>
+
+template <typename T, typename P>
+inline
+std::ostream & operator<<(std::ostream & os, boost::numeric::interval<T,P> const& v)
+{
+  return os << '[' << v.lower() << ".." << v.upper() << '[';
+}
+
+namespace otb
+{
+
+template<typename TImage>
+void
+ClampROIFilter<TImage>
+::ThreadedGenerateData(
+    OutputImageRegionType const& outputRegionForThread,
+    itk::ThreadIdType            threadId)
+{
+  // otbMsgDevMacro("ThreadedGenerateData begin("<<NeatRegionLogger{outputRegionForThread}<<")");
+  using InputIterator   = itk::ImageScanlineConstIterator<InputImageType const>;
+  using OutputIterator  = itk::ImageScanlineIterator<OutputImageType>;
+
+  auto const* input  = this->GetInput();
+  auto      * output = this->GetOutput();
+  assert(input);
+  assert(output);
+  InputIterator  inputIterator (input,  OutputRegionToInputRegion(outputRegionForThread));
+  OutputIterator outputIterator(output, outputRegionForThread);
+
+  auto const& imgRegion = output->GetLargestPossibleRegion();
+  auto const  imgSizeX  = imgRegion.GetSize()[0];
+  auto const  imgSizeY  = imgRegion.GetSize()[1];
+  itk::IndexValueType const  imgEndX   = imgRegion.GetIndex()[0] + imgSizeX;
+  itk::IndexValueType const  imgEndY   = imgRegion.GetIndex()[1] + imgSizeY;
+
+  auto const& size      = outputRegionForThread.GetSize();
+  auto const& index     = outputRegionForThread.GetIndex();
+  auto const  sizeX     = size[0];
+  auto const  sizeY     = size[1];
+  auto const  startX    = index[0];
+  auto const  startY    = index[1];
+  itk::IndexValueType const  endX      = startX + sizeX;
+  itk::IndexValueType const  endY      = startY + sizeY;
+  auto const  thrX1     = std::min(endX, m_thresholdX);
+  auto const  thrX2     = std::min(endX, imgEndX - m_thresholdX);
+  auto const  thrY1     = std::min(endY, m_thresholdYtop);
+  auto const  thrY2     = std::min(endY, imgEndY - m_thresholdYbot);
+
+  assert(thrX1 <= endX && "Iterations shall stay within requested region");
+  assert(thrX2 <= endX && "Iterations shall stay within requested region");
+  assert(thrY1 <= endY && "Iterations shall stay within requested region");
+  assert(thrY2 <= endY && "Iterations shall stay within requested region");
+
+  // using interval_t = boost::numeric::interval<long>;
+  using interval_t = Interval;
+  auto const region      = interval_t{startX, endX};
+  auto const zero_left   = intersect(interval_t{startX, thrX1}, region);
+  auto const copy_middle = intersect(interval_t{thrX1, thrX2},  region);
+  auto const zero_right  = intersect(interval_t{thrX2, endX},   region);
+  otbMsgDevMacro("X in " << zero_left   << " <<-- 0");
+  otbMsgDevMacro("X in " << copy_middle << " <<-- copy input");
+  otbMsgDevMacro("X in " << zero_right  << " <<-- 0");
+  otbMsgDevMacro("Y in ["<<startY<<".."<<thrY1<<"[  <<--- 0");
+
+  auto const nb_z_l = zero_left.upper()   - zero_left.lower();
+  auto const nb_c_m = copy_middle.upper() - copy_middle.lower();
+  auto const nb_z_r = zero_right.upper()  - zero_right.lower();
+  assert(nb_z_l + nb_c_m + nb_z_r == sizeX);
+
+  itk::ProgressReporter progress( this, threadId, outputRegionForThread.GetNumberOfPixels() / sizeY );
+  outputIterator.GoToBegin();
+
+  // TODO: Can we consider that lines are contiguous in memory?
+  // If so, we could have only 2 fill_n and one copy_n!
+  auto y = startY;
+  for (
+      ; y < thrY1
+      ; ++y, outputIterator.NextLine())
+  {
+    // If there is any trimming of first lines, the inputIterator iterator will
+    // directly point to the right region. we shall not increment it!
+    // otbMsgDevMacro("o(" << y << ") <-- 0");
+    assert(! outputIterator.IsAtEnd());
+    outputIterator.GoToBeginOfLine();
+    std::fill_n(&outputIterator.Value(), sizeX, OutputPixelType{});
+    progress.CompletedPixel(); // Completed...Line()
+  }
+  assert(y == thrY1 || y == startY);
+  otbMsgDevMacro("Y in ["<<thrY1<<".."<<thrY2<<"[  <<--- Input");
+  inputIterator.GoToBegin();
+  for (
+      ; y < thrY2
+      ; ++y, inputIterator.NextLine(), outputIterator.NextLine())
+  {
+    // otbMsgDevMacro("o(" << y << ") <-- Input");
+    assert(! inputIterator.IsAtEnd());
+    assert(! outputIterator.IsAtEnd());
+    inputIterator.GoToBeginOfLine();
+    outputIterator.GoToBeginOfLine();
+    auto const t1 = std::fill_n(&outputIterator.Value(), nb_z_l, OutputPixelType{});
+    // If there is any trimming of first columns, the inputIterator iterator
+    // will directly point to the right region. we shall not apply an offset!
+    auto const t2 = std::copy_n(&inputIterator.Value(), nb_c_m, t1);
+    std::fill_n(t2, nb_z_r, OutputPixelType{});
+    progress.CompletedPixel(); // Completed...Line()
+  }
+  assert(y == thrY2 || y == startY);
+  otbMsgDevMacro("Y in ["<<thrY2<<".."<<endY<<"[  <<--- 0");
+  for (
+      ; y < endY
+      ; ++y, outputIterator.NextLine())
+  {
+    // If there is any trimming of last lines, the inputIterator iterator will
+    // directly point to the right region. we shall not increment it!
+    // otbMsgDevMacro("o(" << y << ") <-- 0");
+    assert(! outputIterator.IsAtEnd());
+    outputIterator.GoToBeginOfLine();
+    std::fill_n(&outputIterator.Value(), sizeX, OutputPixelType{});
+    progress.CompletedPixel(); // Completed...Line()
+  }
+  assert(y == endY);
+  otbMsgDevMacro("ThreadedGenerateData end");
+}
+
+template<typename TImage>
+typename ClampROIFilter<TImage>::InputImageRegionType
+ClampROIFilter<TImage>
+::OutputRegionToInputRegion(OutputImageRegionType const& srcRegion)
+{
+  auto const* output = this->GetOutput();
+  assert(output);
+
+  auto const& maxRegion = output->GetLargestPossibleRegion();
+  auto const& maxSize   = maxRegion.GetSize();
+  auto const& maxStart  = maxRegion.GetIndex();
+
+  auto const& reqRegion = srcRegion;
+  auto const& reqSize   = reqRegion.GetSize();
+  auto const& reqStart  = reqRegion.GetIndex();
+
+  // using interval_t = boost::numeric::interval<long>;
+  using interval_t = Interval;
+  auto const maxRegionX = interval_t{
+    maxStart[0]+m_thresholdX,
+    static_cast<itk::IndexValueType>(maxStart[0]+maxSize[0]-m_thresholdX)
+  };
+  auto const maxRegionY = interval_t{
+    maxStart[1]+m_thresholdYtop,
+    static_cast<itk::IndexValueType>(maxStart[1]+maxSize[1]-m_thresholdYbot)
+  };
+
+  auto const reqRegionX = interval_t::OfLength(reqStart[0], reqSize[0]);
+  auto const reqRegionY = interval_t::OfLength(reqStart[1], reqSize[1]);
+#if 0
+  otbMsgDevMacro("OutputRegionToInputRegion: "
+      << "out="<< NeatRegionLogger{reqRegion}
+      << ";    max: x="<<maxRegionX << "  y="<<maxRegionY
+      << ";    req: x="<<reqRegionX << "  y="<<reqRegionY
+      );
+#endif
+
+  auto const inRegionX = intersect(reqRegionX, maxRegionX);
+  auto const inRegionY = intersect(reqRegionY, maxRegionY);
+  // otbMsgDevMacro(" --> ∩X: " << inRegionX << " ∩Y: " << inRegionY);
+
+  const InputIndexType inStart{inRegionX.lower(), inRegionY.lower()};
+  assert(inRegionX.lower() <= inRegionX.upper());
+  assert(inRegionY.lower() <= inRegionY.upper());
+  const InputSizeType inSize{
+    static_cast<unsigned long>(inRegionX.upper()-inRegionX.lower()),
+    static_cast<unsigned long>(inRegionY.upper()-inRegionY.lower())
+  };
+  auto const inRegion = InputImageRegionType{inStart, inSize};
+  otbMsgDevMacro("OutputRegionToInputRegion: out="<< NeatRegionLogger{reqRegion}<<"   --> in="<<NeatRegionLogger{inRegion});
+  return inRegion;
+}
+
+} // otb namespace
+
+#endif  // otbClampROIFilter_hxx
+
diff --git a/Modules/Remote/S1TilingSupportApplications.remote.cmake b/Modules/Remote/S1TilingSupportApplications.remote.cmake
index 402f984e7b..7bafd6d7c2 100644
--- a/Modules/Remote/S1TilingSupportApplications.remote.cmake
+++ b/Modules/Remote/S1TilingSupportApplications.remote.cmake
@@ -27,5 +27,5 @@ https://gitlab.orfeo-toolbox.org/s1-tiling/s1tilingsupportapplications
 "
   GIT_REPOSITORY https://gitlab.orfeo-toolbox.org/s1-tiling/s1tilingsupportapplications
   # Commit on master branch
-  GIT_TAG master
+  GIT_TAG 2.0.0
 )
-- 
GitLab