From b31695cc580db7d0ccb0d5e85df756e7565487a6 Mon Sep 17 00:00:00 2001 From: Julien Michel <julien.michel@orfeo-toolbox.org> Date: Fri, 15 Jan 2010 16:25:48 +0100 Subject: [PATCH] ENH: In TSX calib, theta_loc is now estimated via least-square linear regression on the incidence angle records available --- .../otbTerraSarCalibrationFunctor.h | 27 +-- .../otbTerraSarCalibrationFunctor.txx | 12 +- .../otbTerraSarCalibrationImageFilter.h | 59 +++++- .../otbTerraSarCalibrationImageFilter.txx | 175 ++++++++++++++++-- .../otbTerraSarCalibrationFunctor.cxx | 4 +- 5 files changed, 222 insertions(+), 55 deletions(-) diff --git a/Code/Radiometry/otbTerraSarCalibrationFunctor.h b/Code/Radiometry/otbTerraSarCalibrationFunctor.h index d817c98113..6d6859c777 100644 --- a/Code/Radiometry/otbTerraSarCalibrationFunctor.h +++ b/Code/Radiometry/otbTerraSarCalibrationFunctor.h @@ -98,20 +98,7 @@ public: { return m_CalibrationFactor; }; - - /** Set the local incidence angle */ - void SetLocalIncidentAngle( double val ) - { - m_LocalIncidentAngle = val; - m_SinLocalIncidentAngle = vcl_sin(m_LocalIncidentAngle*CONST_PI_180); - }; - - /** Get the local incidence angle */ - double GetLocalIncidentAngle() const - { - return m_LocalIncidentAngle; - }; - + /** Set the image size */ void SetOriginalProductSize( SizeType size ) { @@ -155,16 +142,16 @@ public: } /** Get the default value */ - void GetDefaultValue(void) + double GetDefaultValue(void) { return m_DefaultValue; } /** Perform the calibration for one pixel (scalar -> modulus image) */ - inline TOutput operator() (const TInput & inPix, IndexType index); + inline TOutput operator() (const TInput & inPix, const IndexType& index, double angle); /** Perform the calibration for one pixel (complex -> complex image) */ - inline std::complex<TOutput> operator() (const std::complex<TInput> & inPix, IndexType index); + inline std::complex<TOutput> operator() (const std::complex<TInput> & inPix, const IndexType& index, double angle); private: /** Return the current range position */ @@ -182,12 +169,6 @@ private: /** Noise record vector */ ImageNoiseType m_NoiseRecord; - /** Sensor local incident angle in degree */ - double m_LocalIncidentAngle; - - /** sin of the LocalIncidentAngle */ - double m_SinLocalIncidentAngle; - /** Image Size */ SizeType m_OriginalProductSize; diff --git a/Code/Radiometry/otbTerraSarCalibrationFunctor.txx b/Code/Radiometry/otbTerraSarCalibrationFunctor.txx index 2426d23d61..78bd9ef877 100644 --- a/Code/Radiometry/otbTerraSarCalibrationFunctor.txx +++ b/Code/Radiometry/otbTerraSarCalibrationFunctor.txx @@ -31,8 +31,6 @@ TerraSarCalibrationFunctor<TInput, TOutput> { // Initialise values m_CalibrationFactor = itk::NumericTraits<double>::Zero; - m_LocalIncidentAngle = itk::NumericTraits<double>::Zero; - m_SinLocalIncidentAngle = itk::NumericTraits<double>::Zero; m_OriginalProductSize.Fill(0); m_UseFastCalibration = false; m_ResultsInDecibels = false; @@ -97,7 +95,7 @@ TerraSarCalibrationFunctor<TInput, TOutput> template <class TInput, class TOutput> TOutput TerraSarCalibrationFunctor<TInput, TOutput> -::operator()(const TInput & inPix, IndexType index) +::operator()(const TInput & inPix, const IndexType& index, double angle) { // Formula: sigma = (Ks.|DN|²-NEBN) * sin Theta_local @@ -110,7 +108,7 @@ TerraSarCalibrationFunctor<TInput, TOutput> // If fast calibration is off, compute noise if(m_UseFastCalibration) { - sigma = beta0 * m_SinLocalIncidentAngle; + sigma = beta0 * vcl_sin(angle); } else { @@ -121,7 +119,7 @@ TerraSarCalibrationFunctor<TInput, TOutput> double NEBN = this->ComputeNoiseEquivalentBetaNaught(currentRange); // Last, apply formula - sigma = (beta0 - NEBN) * m_SinLocalIncidentAngle; + sigma = (beta0 - NEBN) * vcl_sin(angle); // Handle negative sigma case if(sigma <=0) @@ -143,14 +141,14 @@ TerraSarCalibrationFunctor<TInput, TOutput> template <class TInput, class TOutput> std::complex<TOutput> TerraSarCalibrationFunctor<TInput, TOutput> -::operator()(const std::complex<TInput> & inPix, IndexType index) +::operator()(const std::complex<TInput> & inPix, const IndexType& index, double angle) { // First, extract modulus and phase double modulus = vcl_sqrt(inPix.real()*inPix.real() + inPix.imag()*inPix.imag()); double phase = vcl_atan2(inPix.imag(),inPix.real()); // Then, calibrate the modulus - double sigma = this->operator()(modulus,index); + double sigma = this->operator()(modulus,index,angle); // Last, put back the phase std::complex<TOutput> out(std::polar(sigma,phase)); diff --git a/Code/Radiometry/otbTerraSarCalibrationImageFilter.h b/Code/Radiometry/otbTerraSarCalibrationImageFilter.h index 674303f721..8860f845bc 100644 --- a/Code/Radiometry/otbTerraSarCalibrationImageFilter.h +++ b/Code/Radiometry/otbTerraSarCalibrationImageFilter.h @@ -45,8 +45,12 @@ namespace otb * NEBN is interpolated for each range position according to the * polynomial coefficients from the most recent noise record. * - * \f$ \theta_{loc} \f$ is the mean incident angle over the scene. For - * now, this filter does not support more accurate behaviour like + * \f$ \theta_{loc} \f$ is estimated from the incidence angles from + * the metadata following a least-square regression. + * One can also add its own incident angle records using the + * appropriate add method. + * + * For now, this filter does not support more accurate behaviour like * incident angle interpolation from metadata or the Geocoded * Incidence Angle mask optional 1B product (GIM). * @@ -107,6 +111,11 @@ public: typedef typename OutputImageType::InternalPixelType OutputInternalPixelType; typedef typename itk::NumericTraits<InputInternalPixelType>::ValueType InputValueType; typedef typename itk::NumericTraits<OutputInternalPixelType>::ValueType OutputValueType; + typedef typename InputImageType::IndexType IndexType; + + /** Incidence angle samples */ + typedef std::pair<IndexType,double> IncidenceAngleRecordType; + typedef std::vector<IncidenceAngleRecordType> IncidenceAngleRecordVectorType; /** Calibration functor typedef */ typedef typename Functor::TerraSarCalibrationFunctor<InputValueType, @@ -135,10 +144,6 @@ public: /** Calibration Factor */ itkSetMacro(CalibrationFactor,double); itkGetMacro(CalibrationFactor,double); - - /** Sensor local incident angle in degree */ - itkSetMacro(LocalIncidentAngle,double); - itkGetMacro(LocalIncidentAngle,double); /** If fast calibration is On, noise is ignored */ itkSetMacro(UseFastCalibration,bool); @@ -179,6 +184,17 @@ public: */ void ClearNoiseRecords(); + /** + * Add an incidence angle record to estimate the local incidence. + * Angle has to be given in degrees. + */ + void AddIncidenceAngleRecord(const IndexType& index, double angle); + + /** + * Clear the incidence angle records. + */ + void ClearIncidenceAngleRecords(); + protected: /** Constructor */ TerraSarCalibrationImageFilter(); @@ -194,6 +210,10 @@ protected: /** PrintSelf method */ void PrintSelf(std::ostream& os, itk::Indent indent) const; + /** Reimplement the Modified() method to handle the + * ParametersUpToDate flag */ + virtual void Modified(); + private: TerraSarCalibrationImageFilter(const Self&); //purposely not implemented void operator=(const Self&); //purposely not implemented @@ -202,12 +222,19 @@ private: * date */ static bool CompareNoiseRecords(const NoiseRecordType& record1, const NoiseRecordType& record2); + /** Estimate angular plane. The angular is interpolated by a plane + * fitted by least squares to the angular corners. More Appropriate + * methods (using DEM for instance) could be implemented later */ + void EstimateAngularPlaneParameters(); + + /** + * Compute the incidence angle from the index + */ + inline double ComputeIncidenceAngle(const IndexType& index) const; + /** Calibration Factor */ double m_CalibrationFactor; - /** Sensor local incident angle in degree */ - double m_LocalIncidentAngle; - /** Pulse Repetition Frequency */ double m_PRF; @@ -224,10 +251,22 @@ private: NoiseRecordVectorType m_NoiseRecords; /** Default value (for negative sigma) */ - bool m_DefaultValue; + double m_DefaultValue; + + /** Incidence angle records vector */ + IncidenceAngleRecordVectorType m_IncidenceAngleRecords; + + /** Incidence angle linear regression parameters */ + double m_IncidenceAngleAx; + double m_IncidenceAngleAy; + double m_IncidenceAngleOffset; + + /** True if parameters are up-to-date (avoid recomputing) */ + bool m_ParametersUpToDate; }; + } // end namespace otb #ifndef OTB_MANUAL_INSTANTIATION diff --git a/Code/Radiometry/otbTerraSarCalibrationImageFilter.txx b/Code/Radiometry/otbTerraSarCalibrationImageFilter.txx index 424ad42899..5e8db6198a 100644 --- a/Code/Radiometry/otbTerraSarCalibrationImageFilter.txx +++ b/Code/Radiometry/otbTerraSarCalibrationImageFilter.txx @@ -28,6 +28,10 @@ #include "itkImageRegionIterator.h" #include "itkProgressReporter.h" +#include <vnl/algo/vnl_lsqr.h> +#include <vnl/vnl_sparse_matrix_linear_system.h> +#include <vnl/vnl_least_squares_function.h> + namespace otb { /** @@ -36,14 +40,17 @@ namespace otb template <class TInputImage, class TOutputImage> TerraSarCalibrationImageFilter<TInputImage,TOutputImage> ::TerraSarCalibrationImageFilter() : m_CalibrationFactor(itk::NumericTraits<double>::Zero), - m_LocalIncidentAngle(itk::NumericTraits<double>::Zero), - m_PRF(1.), - m_OriginalProductSize(), - m_UseFastCalibration(false), - m_ResultsInDecibels(false), - m_NoiseRecords(), - m_DefaultValue(0.00001) - + m_PRF(1.), + m_OriginalProductSize(), + m_UseFastCalibration(false), + m_ResultsInDecibels(false), + m_NoiseRecords(), + m_DefaultValue(0.00001), + m_IncidenceAngleRecords(), + m_IncidenceAngleAx(0.), + m_IncidenceAngleAy(0.), + m_IncidenceAngleOffset(0.), + m_ParametersUpToDate(false) {} template <class TInputImage, class TOutputImage> @@ -54,6 +61,16 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> this->ClearNoiseRecords(); } +template <class TInputImage, class TOutputImage> +void +TerraSarCalibrationImageFilter<TInputImage,TOutputImage> +::Modified() +{ + // Call Superclass implementation + Superclass::Modified(); + m_ParametersUpToDate = false; +} + template <class TInputImage, class TOutputImage> void TerraSarCalibrationImageFilter<TInputImage,TOutputImage> @@ -64,6 +81,9 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> // Add it to the list m_NoiseRecords.push_back(newRecord); + + // Call modified method + this->Modified(); } template <class TInputImage, class TOutputImage> @@ -72,6 +92,32 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> ::ClearNoiseRecords() { m_NoiseRecords.clear(); + + // Call modified method + this->Modified(); +} + +template <class TInputImage, class TOutputImage> +void +TerraSarCalibrationImageFilter<TInputImage,TOutputImage> +::AddIncidenceAngleRecord(const IndexType& index, double angle) +{ + IncidenceAngleRecordType record(index,angle); + m_IncidenceAngleRecords.push_back(record); + + // Call modified method + this->Modified(); +} + +template <class TInputImage, class TOutputImage> +void +TerraSarCalibrationImageFilter<TInputImage,TOutputImage> +::ClearIncidenceAngleRecords() +{ + m_IncidenceAngleRecords.clear(); + + // Call modified method + this->Modified(); } template <class TInputImage, class TOutputImage> @@ -87,6 +133,12 @@ void TerraSarCalibrationImageFilter<TInputImage,TOutputImage> ::BeforeThreadedGenerateData() { + // Check if we need to update parameters + if(m_ParametersUpToDate) + { + return; + } + // Set the product original size if (m_OriginalProductSize[0] == 0 && m_OriginalProductSize[1] == 0) { @@ -162,11 +214,29 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> } // Incident Angle - if (m_LocalIncidentAngle == itk::NumericTraits<double>::Zero) + if (m_IncidenceAngleRecords.empty()) { if (mdIsAvailable) { - m_LocalIncidentAngle = lImageMetadata->GetMeanIncidenceAngles(this->GetInput()->GetMetaDataDictionary()); + // Retrieve center incidence angle + double centerAngle = lImageMetadata->GetCenterIncidenceAngle(this->GetInput()->GetMetaDataDictionary()); + IndexType centerIndex = lImageMetadata->GetCenterIncidenceAngleIndex(this->GetInput()->GetMetaDataDictionary()); + this->AddIncidenceAngleRecord(centerIndex,centerAngle); + + // Retrieve corners incidence angle + std::vector<double> cangles = lImageMetadata->GetCornersIncidenceAngles(this->GetInput()->GetMetaDataDictionary()); + std::vector<IndexType> cindex = lImageMetadata->GetCornersIncidenceAnglesIndex(this->GetInput()->GetMetaDataDictionary()); + + std::vector<double>::const_iterator angIt = cangles.begin(); + typename std::vector<IndexType>::const_iterator indIt = cindex.begin(); + + // Add each corner angle record as well + while(angIt != cangles.end() && indIt != cindex.end()) + { + this->AddIncidenceAngleRecord(*indIt,*angIt); + ++angIt; + ++indIt; + } } else { @@ -176,6 +246,85 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> // Ensure that noise records are sorted by decreasing acquisition // date std::sort(m_NoiseRecords.begin(),m_NoiseRecords.end(), &Self::CompareNoiseRecords); + + // Estimate angular plane parameters + this->EstimateAngularPlaneParameters(); + + // Mark parameters as up-to-date + m_ParametersUpToDate = true; +} + +template <class TInputImage, class TOutputImage> +void +TerraSarCalibrationImageFilter<TInputImage,TOutputImage> +::EstimateAngularPlaneParameters() +{ + // Check if there are any incidence angle records + if(m_IncidenceAngleRecords.empty()) + { + itkExceptionMacro(<<"No incidence angle records found, can not perform calibration!"); + } + + // If we do not have enough point to do the least square regression, + // set default value as a constant mean angle + if(m_IncidenceAngleRecords.size() < 3) + { + m_IncidenceAngleAx = 0.; + m_IncidenceAngleAy = 0.; + m_IncidenceAngleOffset = 0.; + + typename IncidenceAngleRecordVectorType::const_iterator + aIt = m_IncidenceAngleRecords.begin(); + + // Compute the mean incidence angle + while(aIt != m_IncidenceAngleRecords.end()) + { + m_IncidenceAngleOffset += aIt->second; + ++aIt; + } + // Set the offset to the mean angle + m_IncidenceAngleOffset/=m_IncidenceAngleRecords.size(); + m_IncidenceAngleOffset*= M_PI / 180.; + return; + } + + // Perform the plane least square estimation + unsigned int nbRecords = m_IncidenceAngleRecords.size(); + vnl_sparse_matrix<double> a(nbRecords,3); + vnl_vector<double> b(nbRecords), bestParams(3); + + // Fill the linear system + for(unsigned int i = 0; i < nbRecords; ++i) + { + std::cout<<"Incidence record: "<<m_IncidenceAngleRecords.at(i).first<<" <-> "<<m_IncidenceAngleRecords.at(i).second<<std::endl; + a(i,0) = m_IncidenceAngleRecords.at(i).first[0]; + a(i,1) = m_IncidenceAngleRecords.at(i).first[1]; + a(i,2) = 1.; + b[i] = m_IncidenceAngleRecords.at(i).second * M_PI / 180.; + } + + // Create the linear system + vnl_sparse_matrix_linear_system<double> linearSystem(a,b); + vnl_lsqr linearSystemSolver(linearSystem); + + // And solve it + linearSystemSolver.minimize(bestParams); + + m_IncidenceAngleAx = bestParams[0]; + m_IncidenceAngleAy = bestParams[1]; + m_IncidenceAngleOffset = bestParams[2]; + + std::cout<<"Parameters :" <<bestParams<<std::endl; +} + +template <class TInputImage, class TOutputImage> +double +TerraSarCalibrationImageFilter<TInputImage,TOutputImage> +::ComputeIncidenceAngle(const IndexType& index) const +{ + // Apply the regression + double angle = m_IncidenceAngleAx * index[0] + m_IncidenceAngleAy * index[1] + m_IncidenceAngleOffset; + return angle; } template <class TInputImage, class TOutputImage> @@ -205,7 +354,6 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> // Set up the functor CalibrationFunctorType calibrationFunctor; calibrationFunctor.SetCalibrationFactor(m_CalibrationFactor); - calibrationFunctor.SetLocalIncidentAngle(m_LocalIncidentAngle); calibrationFunctor.SetUseFastCalibration(m_UseFastCalibration); calibrationFunctor.SetResultsInDecibels(m_ResultsInDecibels); calibrationFunctor.SetOriginalProductSize(m_OriginalProductSize); @@ -272,7 +420,7 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> } } // Apply the calibration functor - outputIt.Set( calibrationFunctor( inputIt.Get(), inputIt.GetIndex() ) ); + outputIt.Set( calibrationFunctor( inputIt.Get(), inputIt.GetIndex(), this->ComputeIncidenceAngle(inputIt.GetIndex()) ) ); ++inputIt; ++outputIt; progress.CompletedPixel(); // potential exception thrown here @@ -290,10 +438,11 @@ TerraSarCalibrationImageFilter<TInputImage,TOutputImage> os << indent << "Calibration Factor: " << m_CalibrationFactor << std::endl; os << indent << "PRF: "<<m_PRF <<std::endl; os << indent << "Original product size: "<<m_OriginalProductSize << std::endl; - os << indent << "Sensor local incidence angle: " << m_LocalIncidentAngle << std::endl; os << indent << "Fast calibration: " << (m_UseFastCalibration ? "On" : "Off")<<std::endl; os << indent << "Results in decibels: " << (m_ResultsInDecibels ? "Yes" : "No") << std::endl; os << indent << "Number of noise records: " << m_NoiseRecords.size() <<std::endl; + os << indent << "Number of angle records: " << m_IncidenceAngleRecords.size() << std::endl; + os << indent << "Angle regression: "<<m_IncidenceAngleAx<<"* col + "<<m_IncidenceAngleAx<<" * row +"<<m_IncidenceAngleOffset<<std::endl; } diff --git a/Testing/Code/Radiometry/otbTerraSarCalibrationFunctor.cxx b/Testing/Code/Radiometry/otbTerraSarCalibrationFunctor.cxx index a81652d684..a0fadf9d55 100644 --- a/Testing/Code/Radiometry/otbTerraSarCalibrationFunctor.cxx +++ b/Testing/Code/Radiometry/otbTerraSarCalibrationFunctor.cxx @@ -34,10 +34,10 @@ int otbTerraSarCalibrationFunctor(int argc, char * argv[]) id[0] = 125; id[0] = 150; ScalarType inPix = 150.2; - std::cout << inPix << " -> " << funct.operator()(inPix, id) << std::endl; + std::cout << inPix << " -> " << funct.operator()(inPix, id, 0.) << std::endl; ComplexType inCplxPix(12, 180); - std::cout << inCplxPix << " -> " << funct.operator()(inCplxPix, id) << std::endl; + std::cout << inCplxPix << " -> " << funct.operator()(inCplxPix, id,0.) << std::endl; return EXIT_SUCCESS; } -- GitLab