From dabc50b1d24e60870c1098d299cc9720f58fd23d Mon Sep 17 00:00:00 2001
From: Julien Malik <julien.malik@c-s.fr>
Date: Fri, 18 Feb 2011 08:36:59 +0100
Subject: [PATCH] ADD: add FullyConstrainedLeastSquare image filter

---
 ...tbFullyConstrainedLeastSquareImageFilter.h | 159 ++++++++++++++
 ...FullyConstrainedLeastSquareImageFilter.txx | 201 ++++++++++++++++++
 Testing/CMakeLists.txt                        |  20 ++
 ...FullyConstrainedLeastSquareImageFilter.cxx |  75 +++++++
 Testing/otbHyperTests1.cxx                    |   2 +
 5 files changed, 457 insertions(+)
 create mode 100644 Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.h
 create mode 100644 Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.txx
 create mode 100644 Testing/otbFullyConstrainedLeastSquareImageFilter.cxx

diff --git a/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.h b/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.h
new file mode 100644
index 0000000000..e8be0dcc3b
--- /dev/null
+++ b/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.h
@@ -0,0 +1,159 @@
+/*=========================================================================
+
+  Program:   ORFEO Toolbox
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+
+  Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
+  See OTBCopyright.txt for details.
+
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef __otbFullyConstrainedLeastSquareImageFilter_h
+#define __otbFullyConstrainedLeastSquareImageFilter_h
+
+#include "itkMacro.h"
+#include "itkNumericTraits.h"
+#include "otbUnaryFunctorImageFilter.h"
+#include "vnl/algo/vnl_svd.h"
+#include <boost/shared_ptr.hpp>
+
+namespace otb
+{
+
+
+namespace Functor {
+
+/** \class FullyConstrainedLeastSquareFunctor
+ *
+ * \brief TODO
+ *
+ */
+template<class TInput, class TOutput, class TPrecision>
+class FullyConstrainedLeastSquareFunctor
+{
+public:
+  typedef FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision> Self;
+
+  typedef TInput     InputType;
+  typedef TOutput    OutputType;
+  typedef TPrecision PrecisionType;
+
+  typedef vnl_vector<PrecisionType> VectorType;
+  typedef vnl_matrix<PrecisionType> MatrixType;
+
+  FullyConstrainedLeastSquareFunctor();
+  virtual ~FullyConstrainedLeastSquareFunctor();
+
+  unsigned int GetOutputSize() const;
+
+  bool operator !=(const FullyConstrainedLeastSquareFunctor& other) const;
+
+  bool operator ==(const FullyConstrainedLeastSquareFunctor& other) const;
+
+  void SetMatrix(const MatrixType& U);
+
+  OutputType operator ()(const InputType& in) const;
+
+private:
+
+  static bool IsNonNegative(PrecisionType val)
+  {
+    return val >= 0;
+  }
+
+  typedef vnl_svd<PrecisionType>     SVDType;
+
+  MatrixType     m_U;
+  unsigned int   m_OutputSize;
+};
+}
+
+/** \class FullyConstrainedLeastSquareImageFilter
+ *
+ * \brief Performs fully constrained least squares on each pixel of a VectorImage
+ *
+ * This filter takes as input a multiband image and a matrix.
+ * If the matrix is called $A$, it solves, for each pixel $p$, the system
+ * \f$A \cdot x = p\f$ in the least square sense, with additionnal constraints on the solution
+ * \f$\hat{x}\f$ ensuring positivity (each component is positive) and additivity (the sum of
+ * all components is 1).
+ *
+ * The main use of this filter is to unmix an hyperspectral dataset,
+ * where \f$A\f$ is the mixing matrix, in which each row corresponds to an endmember signature.
+ *
+ * The number of rows in \f$A\f$ must match the input image number of bands.
+ * The number of bands in the output image will be the number of columns of $A$
+ *
+ * References
+ *   "Fully Constrained Least-Squares Based Linear Unmixing." Daniel Heinz,
+ *   Chein-I Chang, and Mark L.G. Althouse. IEEE. 1999.
+ *
+ * \ingroup Streamed
+ * \ingroup Threaded
+ */
+template <class TInputImage, class TOutputImage, class TPrecision>
+class ITK_EXPORT FullyConstrainedLeastSquareImageFilter :
+  public otb::UnaryFunctorImageFilter<TInputImage, TOutputImage,
+      Functor::FullyConstrainedLeastSquareFunctor<typename TInputImage::PixelType,
+          typename TOutputImage::PixelType, TPrecision> >
+{
+public:
+  /** Standard class typedefs. */
+  typedef FullyConstrainedLeastSquareImageFilter Self;
+  typedef otb::UnaryFunctorImageFilter
+     <TInputImage,
+      TOutputImage,
+      Functor::FullyConstrainedLeastSquareFunctor<
+          typename TInputImage::PixelType,
+          typename TOutputImage::PixelType,
+          TPrecision>
+     >                                 Superclass;
+  typedef itk::SmartPointer<Self>       Pointer;
+  typedef itk::SmartPointer<const Self> ConstPointer;
+
+  typedef Functor::FullyConstrainedLeastSquareFunctor<
+      typename TInputImage::PixelType,
+      typename TOutputImage::PixelType,
+      TPrecision> FunctorType;
+  typedef typename FunctorType::MatrixType MatrixType;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(FullyConstrainedLeastSquareImageFilter, otb::UnaryFunctorImageFilter);
+
+  /** Pixel types. */
+  typedef typename TInputImage::PixelType  InputPixelType;
+  typedef typename TOutputImage::PixelType OutputPixelType;
+
+  void SetMatrix(const MatrixType& m);
+
+protected:
+  FullyConstrainedLeastSquareImageFilter();
+
+  virtual ~FullyConstrainedLeastSquareImageFilter();
+
+  void PrintSelf(std::ostream& os, itk::Indent indent) const;
+
+private:
+  FullyConstrainedLeastSquareImageFilter(const Self &); //purposely not implemented
+  void operator =(const Self&); //purposely not implemented
+
+};
+
+} // end namespace otb
+
+#ifndef OTB_MANUAL_INSTANTIATION
+#include "otbFullyConstrainedLeastSquareImageFilter.txx"
+#endif
+
+#endif
+
diff --git a/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.txx b/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.txx
new file mode 100644
index 0000000000..7c457a2f12
--- /dev/null
+++ b/Code/Hyperspectral/otbFullyConstrainedLeastSquareImageFilter.txx
@@ -0,0 +1,201 @@
+/*=========================================================================
+
+  Program:   ORFEO Toolbox
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+
+  Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
+  See OTBCopyright.txt for details.
+
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef __otbFullyConstrainedLeastSquareImageFilter_txx
+#define __otbFullyConstrainedLeastSquareImageFilter_txx
+
+#include "otbFullyConstrainedLeastSquareImageFilter.h"
+#include <algorithm>
+
+namespace otb
+{
+
+namespace Functor
+{
+
+template <class TInput, class TOutput, class TPrecision>
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::FullyConstrainedLeastSquareFunctor()
+ : m_OutputSize(0)
+{
+}
+
+template <class TInput, class TOutput, class TPrecision>
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::~FullyConstrainedLeastSquareFunctor()
+{
+}
+
+
+template <class TInput, class TOutput, class TPrecision>
+unsigned int
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::GetOutputSize() const
+{
+  return m_OutputSize;
+}
+
+template <class TInput, class TOutput, class TPrecision>
+bool
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::operator !=(const Self& other) const
+{
+  return true;
+}
+
+template <class TInput, class TOutput, class TPrecision>
+bool
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::operator ==(const Self& other) const
+{
+  return false;
+}
+
+template <class TInput, class TOutput, class TPrecision>
+void
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::SetMatrix(const MatrixType& U)
+{
+  m_U = U;
+  m_OutputSize = m_U.cols();
+}
+
+template <class TInput, class TOutput, class TPrecision>
+typename FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>::OutputType
+FullyConstrainedLeastSquareFunctor<TInput, TOutput, TPrecision>
+::operator ()(const InputType& in) const
+{
+  // TODO : support different types between input and output ?
+  VectorType inVector(in.GetDataPointer(), in.Size());
+  VectorType out(m_OutputSize);
+
+  MatrixType U = m_U;
+  unsigned int count = m_U.cols();
+
+  typedef std::vector<unsigned int> IndiciesType;
+  IndiciesType indicies;
+  for (unsigned int i = 0; i < m_OutputSize; ++i)
+    {
+    indicies.push_back(i);
+    }
+
+  for (count = m_U.cols(); count > 0; --count)
+    {
+     // First, solve the unconstrained least square system
+     VectorType als_hat = SVDType(U).solve(inVector);
+
+     MatrixType Z(1, count);
+     Z.fill(itk::NumericTraits<PrecisionType>::One);
+     MatrixType Zt = Z.transpose();
+     MatrixType UtU = U.transpose() * U;
+     MatrixType UtUinv = SVDType(UtU).inverse();
+     MatrixType S = UtUinv * Zt;
+
+     // Compute correction term needed for full additivity constraint
+     PrecisionType correctionTerm = (S * SVDType( Z * S ).inverse())(0,0) * ((Z * als_hat)[0] - 1);
+
+     // Remove the correction term from the unconstrained solution
+     VectorType afcls_hat = als_hat - correctionTerm;
+
+     // If everybody is positive, it is finished
+     if ( std::count_if(afcls_hat.begin(), afcls_hat.end(), Self::IsNonNegative) == count )
+       {
+       out.fill(0);
+       for (unsigned int j = 0; j < indicies.size(); ++j)
+         {
+         out[ indicies[j] ] = afcls_hat[ j ];
+         }
+       break;
+       }
+
+     // Multiply negative elements by their counterpart in the S vector
+     // and find max(abs(afcls_hat)) for indicies where elements are negative
+
+     unsigned int maxIdx = 0;
+     PrecisionType maxi = itk::NumericTraits<PrecisionType>::min();
+
+     for (unsigned int j = 0; j < afcls_hat.size(); ++j)
+      {
+      if (afcls_hat[j] < 0)
+        {
+        afcls_hat[j] = afcls_hat[j] / S(j,0);
+
+        if (vcl_abs(afcls_hat[j]) > maxi)
+          {
+          maxi = vcl_abs(afcls_hat[j]);
+          maxIdx = j;
+          }
+        }
+      }
+
+     // Remove column maxIdx from U
+     MatrixType Unew(U.rows(), U.cols() - 1);
+     for (unsigned int Ucol = 0, UcolNew = 0; Ucol < U.cols(); Ucol++)
+       {
+       if (Ucol != maxIdx)
+         {
+         Unew.set_column(UcolNew, U.get_column(Ucol));
+         UcolNew++;
+         }
+       }
+
+     // Remove maxIdx-th element from indicies
+     IndiciesType::iterator itToRemove = indicies.begin() + maxIdx;
+     indicies.erase(itToRemove);
+
+     // Update U
+     U = Unew;
+    }
+
+  // Convert to OutputType (vnl_vector -> VariableLengthVector)
+  return OutputType(out.data_block(), out.size());
+}
+
+}
+
+template <class TInputImage, class TOutputImage, class TPrecision>
+FullyConstrainedLeastSquareImageFilter<TInputImage, TOutputImage, TPrecision>
+::FullyConstrainedLeastSquareImageFilter()
+{
+}
+
+template <class TInputImage, class TOutputImage, class TPrecision>
+FullyConstrainedLeastSquareImageFilter<TInputImage, TOutputImage, TPrecision>
+::~FullyConstrainedLeastSquareImageFilter()
+{
+}
+
+template <class TInputImage, class TOutputImage, class TPrecision>
+void
+FullyConstrainedLeastSquareImageFilter<TInputImage, TOutputImage, TPrecision>
+::SetMatrix(const MatrixType& m)
+{
+  this->GetFunctor().SetMatrix(m);
+  this->Modified();
+}
+
+template <class TInputImage, class TOutputImage, class TPrecision>
+void
+FullyConstrainedLeastSquareImageFilter<TInputImage, TOutputImage, TPrecision>
+::PrintSelf(std::ostream& os, itk::Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+}
+
+} // end namespace
+
+#endif
diff --git a/Testing/CMakeLists.txt b/Testing/CMakeLists.txt
index a8cff678b4..b34eaa27bf 100644
--- a/Testing/CMakeLists.txt
+++ b/Testing/CMakeLists.txt
@@ -10,6 +10,7 @@ SET( otbHyperTests1_SRC
   otbStreamingStatisticsVectorImageFilter2.cxx
   otbMatrixMultiplyImageFilter.cxx
   otbUnConstrainedLeastSquareImageFilter.cxx
+  otbFullyConstrainedLeastSquareImageFilter.cxx
   otbVectorImageToMatrixImageFilter.cxx
   otbProjectiveProjection.cxx
   otbVCA.cxx
@@ -99,6 +100,25 @@ ADD_TEST(bfTvUnConstrainedLeastSquareImageFilter
          ${TEMP}/bfUnConstrainedLeastSquareImageFilter.tif)
 
 
+ADD_TEST(bfTuFullyConstrainedLeastSquareImageFilterNew
+         ${TESTEXE_DIR}/otbHyperTests1
+         otbFullyConstrainedLeastSquareImageFilterNewTest)
+
+ADD_TEST(bfTvFullyConstrainedLeastSquareImageFilter_SYNTHETIC
+         ${TESTEXE_DIR}/otbHyperTests1
+         otbFullyConstrainedLeastSquareImageFilterTest
+         ${DATA}/SYNTHETIC100/hsi_cube.tif
+         ${DATA}/SYNTHETIC100/endmembers.tif
+         ${TEMP}/bfFullyConstrainedLeastSquareImageFilter.tif)
+
+
+ADD_TEST(bfTvFullyConstrainedLeastSquareImageFilter_AVIRIS512
+         ${TESTEXE_DIR}/otbHyperTests1
+         otbFullyConstrainedLeastSquareImageFilterTest
+         ${DATA}/AVIRIS/extract512/f970619t01p02_r02_sc01_goodbands.hdr
+         ${TEMP}/aviris_vcaimagefilter_13.hdr
+         ${TEMP}/bfFullyConstrainedLeastSquareImageFilter_AVIRIS512.tif)
+
 
 
 ADD_TEST(bfTuVectorImageToMatrixNew
diff --git a/Testing/otbFullyConstrainedLeastSquareImageFilter.cxx b/Testing/otbFullyConstrainedLeastSquareImageFilter.cxx
new file mode 100644
index 0000000000..2010f3c9ce
--- /dev/null
+++ b/Testing/otbFullyConstrainedLeastSquareImageFilter.cxx
@@ -0,0 +1,75 @@
+/*=========================================================================
+
+  Program:   ORFEO Toolbox
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+
+  Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
+  See OTBCopyright.txt for details.
+
+
+  This software is distributed WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+  PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#include "otbFullyConstrainedLeastSquareImageFilter.h"
+
+#include "otbVectorImage.h"
+#include "otbImageFileReader.h"
+#include "otbImageFileWriter.h"
+#include "otbVectorImageToMatrixImageFilter.h"
+
+const unsigned int Dimension = 2;
+typedef double PixelType;
+
+typedef otb::VectorImage<PixelType, Dimension> ImageType;
+typedef otb::ImageFileReader<ImageType> ReaderType;
+typedef otb::FullyConstrainedLeastSquareImageFilter<ImageType,ImageType,double> FullyConstrainedLeastSquareSolverType;
+typedef otb::VectorImageToMatrixImageFilter<ImageType> VectorImageToMatrixImageFilterType;
+typedef otb::ImageFileWriter<ImageType> WriterType;
+
+int otbFullyConstrainedLeastSquareImageFilterNewTest(int argc, char * argv[])
+{
+  FullyConstrainedLeastSquareSolverType::Pointer filter = FullyConstrainedLeastSquareSolverType::New();
+  std::cout << filter << std::endl;
+  return EXIT_SUCCESS;
+}
+
+int otbFullyConstrainedLeastSquareImageFilterTest(int argc, char * argv[])
+{
+  const char * inputImage = argv[1];
+  const char * inputEndmembers = argv[2];
+  const char * outputImage = argv[3];
+
+  ReaderType::Pointer readerImage = ReaderType::New();
+  readerImage->SetFileName(inputImage);
+
+  ReaderType::Pointer readerEndMembers = ReaderType::New();
+  readerEndMembers->SetFileName(inputEndmembers);
+  VectorImageToMatrixImageFilterType::Pointer endMember2Matrix = VectorImageToMatrixImageFilterType::New();
+  endMember2Matrix->SetInput(readerEndMembers->GetOutput());
+
+  endMember2Matrix->Update();
+
+  typedef VectorImageToMatrixImageFilterType::MatrixType MatrixType;
+  MatrixType endMembers = endMember2Matrix->GetMatrix();
+  MatrixType pinv = vnl_matrix_inverse<double>(endMembers);
+
+  FullyConstrainedLeastSquareSolverType::Pointer unmixer = \
+      FullyConstrainedLeastSquareSolverType::New();
+
+  unmixer->SetInput(readerImage->GetOutput());
+  unmixer->SetMatrix(endMember2Matrix->GetMatrix());
+
+  unmixer->Update();
+
+  WriterType::Pointer writer = WriterType::New();
+  writer->SetFileName(outputImage);
+  writer->SetInput(unmixer->GetOutput());
+  writer->Update();
+
+  return EXIT_SUCCESS;
+}
diff --git a/Testing/otbHyperTests1.cxx b/Testing/otbHyperTests1.cxx
index 4cf869af24..d06c4c65ff 100644
--- a/Testing/otbHyperTests1.cxx
+++ b/Testing/otbHyperTests1.cxx
@@ -32,6 +32,8 @@ void RegisterTests()
   REGISTER_TEST(otbMatrixMultiplyImageFilterTest);
   REGISTER_TEST(otbUnConstrainedLeastSquareImageFilterNewTest);
   REGISTER_TEST(otbUnConstrainedLeastSquareImageFilterTest);
+  REGISTER_TEST(otbFullyConstrainedLeastSquareImageFilterNewTest);
+  REGISTER_TEST(otbFullyConstrainedLeastSquareImageFilterTest);
   REGISTER_TEST(otbVectorImageToMatrixNewTest);
   REGISTER_TEST(otbVectorImageToMatrixTest);
   REGISTER_TEST(vahineVCA);
-- 
GitLab