/* * Copyright (C) 2005-2019 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 mvdHistogramModel_h #define mvdHistogramModel_h // // Configuration include. //// Included at first position before any other ones. #include "ConfigureMonteverdi.h" #include "OTBMonteverdiCoreExport.h" /*****************************************************************************/ /* INCLUDE SECTION */ // // Qt includes (sorted by alphabetic order) //// Must be included before system/custom includes. // // System includes (sorted by alphabetic order) #include // // ITK includes (sorted by alphabetic order) #include "itkHistogram.h" // // OTB includes (sorted by alphabetic order) #include "otbObjectList.h" // // Monteverdi includes (sorted by alphabetic order) #include "mvdAbstractModel.h" #include "mvdAlgorithm.h" #include "mvdCore.h" #include "mvdSerializableInterface.h" #define HISTOGRAM_CURVE_TYPE 2 #define USE_FULL_IMAGE_FOR_PASS_1 0 /*****************************************************************************/ /* PRE-DECLARATION SECTION */ // // External classes pre-declaration. namespace { } namespace mvd { namespace { } // // Internal classes pre-declaration. class AbstractImageModel; /*****************************************************************************/ /* CLASS DEFINITION SECTION */ /** * \class HistogramModel * * \ingroup OTBMonteverdiCore */ class OTBMonteverdiCore_EXPORT HistogramModel : public AbstractModel, private SerializableInterface { /*-[ QOBJECT SECTION ]-----------------------------------------------------*/ Q_OBJECT; /*-[ PUBLIC SECTION ]------------------------------------------------------*/ // // Public types & constants. public: /** */ static const int PRECISION = 4; /** */ typedef // itk::NumericTraits< T >::FloatType and // itk::NumericTraits< T >::RealType do not depend on template // parameter T. They are always typedef, respectively, as float // and double. // // So, itk::NumericTraits< DefaultImageType::InternalPixelType // >::RealType is equivalent to itk::NumericTraits< float // >::RealType which is always an alias of double. // // This typedef is used for compatibility with // itk::Histogram<>::MeasurementType. itk::NumericTraits::RealType RealType; /** */ typedef RealType MeasurementType; /** * \class BuildContext * \brief WIP. * \ingroup OTBMonteverdiCore */ class BuildContext { // // Public methods. public: /** \brief Constructor. */ BuildContext(bool isBeingStored, const QString& filename = QString()) : m_Filename(filename), m_IsBeingStored(isBeingStored) { } /** */ inline bool IsBeingStored() const { return m_IsBeingStored; } // // Public attributes public: QString m_Filename; private: bool m_IsBeingStored; }; // // Public methods. public: /** */ static RealType GetEpsilon(); /** \brief Constructor. */ HistogramModel(QObject* p = NULL); /** \brief Destructor. */ ~HistogramModel() override; /** */ bool IsValid() const; /** */ bool IsMonoValue() const; /** */ MeasurementType Quantile(CountType band, double p) const; /** */ MeasurementType Quantile(CountType band, double p, Bound bound) const; /** */ double Percentile(CountType band, MeasurementType intensity, Bound bound) const; /** */ inline VectorPixelType GetMinPixel() const; /** */ inline VectorPixelType GetMaxPixel() const; /** */ inline size_t GetDataCount(CountType band) const; /** */ void GetData(CountType band, double* const x, double* const y, double& xMin, double& xMax, double& yMin, double& yMax) const; /*-[ SIGNALS SECTION ]-----------------------------------------------------*/ // // Signals. signals: /*-[ PROTECTED SECTION ]---------------------------------------------------*/ // // Protected methods. protected: // // AbstractModel methods. /** */ void virtual_BuildModel(void* context = NULL) override; // // Protected attributes. protected: /*-[ PRIVATE SECTION ]-----------------------------------------------------*/ // // Private constants. private: // // Private types. private: /** */ typedef itk::Statistics::Histogram Histogram; /** */ typedef otb::ObjectList HistogramList; // // Private methods. private: /** */ /* template< typename TImage > void template_BuildModel_I(); */ /** */ template void template_BuildModel_M(BuildContext* = NULL); // // SerializableInterface methods. // void virtual_Read(QIODevice* device) override; void virtual_Write(QIODevice& device) const override; // // Private attributes. private: /** */ HistogramList::Pointer m_Histograms; /** */ DefaultImageType::PixelType m_MinPixel; /** */ DefaultImageType::PixelType m_MaxPixel; /*-[ PRIVATE SLOTS SECTION ]-----------------------------------------------*/ // // Slots. private slots: }; } // end namespace 'mvd' /*****************************************************************************/ /* INLINE SECTION */ // // ITK includes (sorted by alphabetic order) // // OTB includes (sorted by alphabetic order) #include "otbStreamingHistogramVectorImageFilter.h" #include "otbStreamingMinMaxVectorImageFilter.h" #include "otbStreamingStatisticsVectorImageFilter.h" // // Monteverdi includes (sorted by alphabetic order) #include "mvdAbstractImageModel.h" namespace mvd { /*******************************************************************************/ inline DefaultImageType::PixelType HistogramModel::GetMinPixel() const { return m_MinPixel; } /*******************************************************************************/ inline DefaultImageType::PixelType HistogramModel::GetMaxPixel() const { return m_MaxPixel; } /*******************************************************************************/ inline size_t HistogramModel::GetDataCount(CountType band) const { // Get histogram of band. Histogram::Pointer histogram(m_Histograms->GetNthElement(band)); assert(!histogram.IsNull()); // Get number of bins for each dimension. Histogram::SizeType size(histogram->GetSize()); // Ensure dimension is 1. assert(histogram->GetMeasurementVectorSize() == 1); // There are twince number of points than number of bins. #if HISTOGRAM_CURVE_TYPE == 0 return 2 * size[0]; #elif HISTOGRAM_CURVE_TYPE == 1 return size[0]; #elif HISTOGRAM_CURVE_TYPE == 2 return 4 * size[0]; #else assert(false && "Unknown HISTOGRAM_CURVE_TYPE value"); #endif } /*******************************************************************************/ /* template< typename TImage > void HistogramModel ::template_BuildModel_I() { QTime lMain; QTime lPass1; QTime lPass2; lMain.start(); qDebug() << tr( "%1: Generating histogram (I)..." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ); AbstractImageModel* imageModel = qobject_cast< AbstractImageModel* >( parent() ); assert( imageModel!=NULL ); // // 1st pass: process min/MAX for each band. qDebug() << tr( "%1: Pass #1 - finding pixel min/maxes..." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ); lPass1.start(); // Connect min/MAX pipe-section. typedef otb::StreamingMinMaxVectorImageFilter< TImage > MinMaxFilter; typename MinMaxFilter::Pointer filterMinMax( MinMaxFilter::New() ); filterMinMax->SetInput( // otb::DynamicCast< TImage >( imageModel->ToImageBase() ) imageModel->ToImageBase() ); filterMinMax->Update(); // Extract-convert-remember min/MAX intensities for each band. m_MinPixel = filterMinMax->GetMinimum(); m_MaxPixel = filterMinMax->GetMaximum(); qDebug() << tr( "%1: Pass #1 - done (%2 ms)." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ) .arg( lPass1.elapsed() ); // // 2nd pass: compute histogram. qDebug() << tr( "%1: Pass #2 - computing histogram..." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ); lPass2.start(); // Connect histogram-generator pipe-section. typedef otb::StreamingHistogramVectorImageFilter< TImage > HistogramFilter; typename HistogramFilter::Pointer histogramFilter( HistogramFilter::New() ); histogramFilter->SetInput( // otb::DynamicCast< TImage >( imageModel->ToImageBase() ) imageModel->ToImageBase() ); // Setup histogram filter. histogramFilter->GetFilter()->SetHistogramMin( m_MinPixel ); histogramFilter->GetFilter()->SetHistogramMax( m_MaxPixel ); histogramFilter->SetNumberOfBins( HistogramModel::BINS_OVERSAMPLING_RATE * 256 ); histogramFilter->GetFilter()->SetSubSamplingRate( 1 ); // Go. histogramFilter->Update(); qDebug() << tr( "%1: Pass #2 - done (%2 ms)." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ) .arg( lPass2.elapsed() ); // // Reference result. m_Histograms = histogramFilter->GetHistogramList(); qDebug() << tr( "%1: Histogram (I) generated (%2 ms)." ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ) .arg( lMain.elapsed() ); } */ /*******************************************************************************/ template void HistogramModel::template_BuildModel_M(BuildContext*) { QTime lMain; QTime lPass1; QTime lPass2; lMain.start(); qDebug() << tr("%1: Generating histogram (M)...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)); #if USE_FULL_IMAGE_FOR_PASS_1 TImageModel* parentImageModel = qobject_cast(parent()); #else // USE_FULL_IMAGE_FOR_PASS_1 AbstractImageModel* parentImageModel = qobject_cast(parent()); #endif // USE_FULL_IMAGE_FOR_PASS_1 assert(parentImageModel != NULL); ImageProperties* imageProperties = parentImageModel->GetProperties(); assert(imageProperties != NULL); TImageModel* imageModel = parentImageModel->GetQuicklookModel(); if (imageModel == NULL) imageModel = qobject_cast(parentImageModel); assert(imageModel != NULL); CountType components = imageModel->ToImage()->GetNumberOfComponentsPerPixel(); assert(components > 0); // Always initialize min and Max pixels. m_MinPixel = DefaultImageType::PixelType(components); m_MaxPixel = DefaultImageType::PixelType(components); m_MinPixel.Fill(0); m_MaxPixel.Fill(1); try { // 1st pass: process min/MAX for each band. qDebug() << QString("%1: Pass #1 - finding pixel min/maxes...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)); lPass1.start(); // Define histogram-filter type. typedef otb::StreamingHistogramVectorImageFilter HistogramFilter; // Connect statistics pipe-section. typedef otb::StreamingStatisticsVectorImageFilter StatisticsFilter; typename StatisticsFilter::Pointer filterStats(StatisticsFilter::New()); #if USE_FULL_IMAGE_FOR_PASS_1 filterStats->SetInput(parentImageModel->ToImage()); #else // USE_FULL_IMAGE_FOR_PASS_1 filterStats->SetInput(imageModel->ToImage()); #endif // USE_FULL_IMAGE_FOR_PASS_1 filterStats->SetEnableMinMax(true); filterStats->SetIgnoreUserDefinedValue(imageProperties->IsNoDataEnabled()); filterStats->SetUserIgnoredValue(imageProperties->GetNoData()); // Connect statistics pipe-section. filterStats->Update(); // qDebug() << "min:" << m_MinPixel << "max:" << m_MaxPixel; // std::cout << "min:" << m_MinPixel << "\tmax:" << m_MaxPixel << std::endl; // Extract-convert-remember min/MAX intensities for each band. m_MinPixel = filterStats->GetMinimum(); m_MaxPixel = filterStats->GetMaximum(); // Define corrected min/MAX pixels. DefaultImageType::PixelType minPixel(m_MinPixel); DefaultImageType::PixelType maxPixel(m_MaxPixel); // Extract sigmas for each band from covariance matrix. typename StatisticsFilter::MatrixType covariance(filterStats->GetFilter()->GetCovariance()); typename HistogramFilter::FilterType::CountVectorType bins(components); typename StatisticsFilter::RealPixelType sums(filterStats->GetSum()); typename StatisticsFilter::RealPixelType means(filterStats->GetMean()); for (CountType i = 0; i < components; ++i) { RealType n = sums[i] / means[i]; // qDebug() << "#" << i << ": " << n; if (n <= 1.0) { bins[i] = 1; } else { RealType sigma = sqrt(covariance(i, i)); // qDebug() << "#" << i << ":" << sigma; assert(sigma >= 0.0); if (sigma <= 0.0) { bins[i] = 1; } else { // Scott's formula // See http://en.wikipedia.org/wiki/Histogram#Number_of_bins_and_width RealType h = 3.5 * sigma / pow(n, 1.0 / 3.0); /* qDebug() << "#" << i << ": h = pow(" << n << "," << 1.0 / 3.0 << ") =" << h; qDebug() << "#" << i << ": bins[" << i << "] = ceil( (" << m_MaxPixel[ i ] << "-" << m_MinPixel[ i ] << ") / " << h << ") =" << ceil( ( m_MaxPixel[ i ] - m_MinPixel[ i ] ) / h ); */ bins[i] = ceil((maxPixel[i] - minPixel[i]) / h); // at least 1 bin is needed bins[i] = std::max(bins[i], 1U); } } // MANTIS-1275 // { if (minPixel[i] == maxPixel[i]) { double epsilon = HistogramModel::GetEpsilon(); // make sure the epsilon is not hidden when using large values if (boost::is_floating_point::value) { double absValue = std::abs(minPixel[i]); // compute smallest epsilon for absolute pixel value (1.5 factor is for safety) double limitEpsilon = absValue * (double)std::numeric_limits::epsilon() * 1.5; epsilon = std::max(epsilon, limitEpsilon); } double lowerBound; // With C++11, we can use std::numeric_limits<>::lowest() if (boost::is_floating_point::value) { lowerBound = epsilon - std::numeric_limits::max(); } else { lowerBound = std::numeric_limits::min() + epsilon; } double upperBound = std::numeric_limits::max() - epsilon; if (minPixel[i] >= lowerBound) minPixel[i] -= epsilon; if (maxPixel[i] <= upperBound) maxPixel[i] += epsilon; } // } // } // std::cout << "Number of bins : "<< bins << std::endl; qDebug() << QString("%1: Pass #1 - done (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lPass1.elapsed()); // // 2nd pass: compute histogram. qDebug() << QString("%1: Pass #2 - computing histogram...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)); lPass2.start(); // Connect histogram-generator pipe-section. typename HistogramFilter::Pointer histogramFilter(HistogramFilter::New()); histogramFilter->SetInput(imageModel->ToImage()); // Setup histogram filter. histogramFilter->GetFilter()->SetHistogramMin(minPixel); histogramFilter->GetFilter()->SetHistogramMax(maxPixel); histogramFilter->GetFilter()->SetNumberOfBins(bins); histogramFilter->GetFilter()->SetSubSamplingRate(1); histogramFilter->GetFilter()->SetNoDataFlag(imageProperties->IsNoDataEnabled()); histogramFilter->GetFilter()->SetNoDataValue(imageProperties->GetNoData()); // Go. histogramFilter->Update(); qDebug() << QString("%1: Pass #2 - done (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lPass2.elapsed()); // // Reference result. m_Histograms = histogramFilter->GetHistogramList(); qDebug() << QString("%1: Histogram (M) generated (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lMain.elapsed()); } catch (const std::exception&) { qWarning() << tr("Zero relevant pixels found when computing histogram (probably because of no-data settings)"); if (imageProperties->IsNoDataEnabled()) { DefaultImageType::PixelType::ValueType value(imageProperties->GetNoData()); m_MinPixel.Fill(value); m_MaxPixel.Fill(value); } qDebug() << QString("%1: Pass #1 - aborted (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lPass1.elapsed()); qDebug() << QString("%1: Pass #2 - aborted (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lPass2.elapsed()); qDebug() << QString("%1: No Histogram (M) has been generated (%2 ms).").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(lMain.elapsed()); } } } // end namespace 'mvd' #endif // mvdHistogramModel_h