diff --git a/Modules/Applications/AppSegmentation/app/otbSmallRegionsMerging.cxx b/Modules/Applications/AppSegmentation/app/otbSmallRegionsMerging.cxx index 4112c9da433f7c10337368dbd75a6fa64800a97d..1693322743508d869f31ad8e5019a5b95650ee30 100644 --- a/Modules/Applications/AppSegmentation/app/otbSmallRegionsMerging.cxx +++ b/Modules/Applications/AppSegmentation/app/otbSmallRegionsMerging.cxx @@ -45,12 +45,14 @@ public: typedef UInt32ImageType LabelImageType; typedef LabelImageType::InternalPixelType LabelImagePixelType; - //typedef otb::StreamingStatisticsImageFilter<LabelImageType> StatisticsImageFilterType; - typedef otb::StreamingStatisticsMapFromLabelImageFilter<ImageType, LabelImageType> StatisticsMapFromLabelImageFilterType; + typedef otb::StreamingStatisticsMapFromLabelImageFilter + <ImageType, LabelImageType> StatisticsMapFromLabelImageFilterType; - typedef otb::LabelImageSmallRegionMergingFilter<LabelImageType> LabelImageSmallRegionMergingFilterType; + typedef otb::LabelImageSmallRegionMergingFilter<LabelImageType> + LabelImageSmallRegionMergingFilterType; - typedef itk::ChangeLabelImageFilter<LabelImageType,LabelImageType> ChangeLabelImageFilterType; + typedef itk::ChangeLabelImageFilter<LabelImageType,LabelImageType> + ChangeLabelImageFilterType; itkNewMacro(Self); itkTypeMacro(Merging, otb::Application); @@ -61,34 +63,43 @@ private: void DoInit() override { SetName("SmallRegionsMerging"); - SetDescription("This application merges small regions of a segmentation result to connected region."); + SetDescription("This application merges small regions of a segmentation " + "result to connected region."); SetDocName("Small Region Merging"); - SetDocLongDescription("Given a segmentation result and the original image, it will" - " merge segments whose size in pixels is lower than minsize parameter" - " with the adjacent segments with the adjacent segment with closest" - " radiometry and acceptable size.\n\n" - "Small segments will be processed by increasing size: first all segments" - " for which area is equal to 1 pixel will be merged with adjacent" - " segments, then all segments of area equal to 2 pixels will be processed," - " until segments of area minsize."); + SetDocLongDescription("Given a segmentation result and the original image," + " it will merge segments whose size in pixels is" + " lower than minsize parameter with the adjacent" + " segments with the adjacent segment with closest" + " radiometry and acceptable size. \n\n" + "Small segments will be processed by increasing size:" + " first all segments for which area is equal to 1" + " pixel will be merged with adjacent segments, then" + " all segments of area equal to 2 pixels will be" + " processed, until segments of area minsize."); - SetDocLimitations("This application is more efficient if the labels are contiguous, starting from 0."); SetDocAuthors("OTB-Team"); - SetDocSeeAlso( "Segmentation"); + SetDocSeeAlso("Segmentation"); AddDocTag(Tags::Segmentation); AddParameter(ParameterType_InputImage, "in", "Input image"); - SetParameterDescription( "in", "The input image, containing initial spectral signatures corresponding to the segmented image (inseg)." ); - AddParameter(ParameterType_InputImage, "inseg", "Segmented image"); - SetParameterDescription( "inseg", "Segmented image where each pixel value is the unique integer label of the segment it belongs to." ); + SetParameterDescription( "in", "The input image, containing initial" + " spectral signatures corresponding to the segmented image (inseg)." ); + AddParameter(ParameterType_InputImage, "inseg", "Segmented image"); + SetParameterDescription( "inseg", "Segmented image where each pixel value" + " is the unique integer label of the segment it belongs to." ); AddParameter(ParameterType_OutputImage, "out", "Output Image"); - SetParameterDescription( "out", "The output image. The output image is the segmented image where the minimal segments have been merged." ); + SetParameterDescription( "out", "The output image. The output image is the" + " segmented image where the minimal segments have been merged." ); SetDefaultOutputPixelType("out",ImagePixelType_uint32); AddParameter(ParameterType_Int, "minsize", "Minimum Segment Size"); - SetParameterDescription("minsize", "Minimum Segment Size. If, after the segmentation, a segment is of size strictly lower than this criterion, the segment is merged with the segment that has the closest sepctral signature."); + SetParameterDescription("minsize", "Minimum Segment Size. If, after the " + " segmentation, a segment is of size strictly lower than this criterion," + " the segment is merged with the segment that has the closest sepctral" + " signature."); + SetDefaultParameterInt("minsize", 50); SetMinimumParameterIntValue("minsize", 1); MandatoryOff("minsize"); @@ -123,7 +134,8 @@ private: auto labelStatsFilter = StatisticsMapFromLabelImageFilterType::New(); labelStatsFilter->SetInput(imageIn); labelStatsFilter->SetInputLabelImage(labelIn); - AddProcess(labelStatsFilter->GetStreamer() , "Computing stats on input image ..."); + AddProcess(labelStatsFilter->GetStreamer() , "Computing stats on input" + " image ..."); labelStatsFilter->Update(); // Convert Map to Unordered map @@ -135,18 +147,19 @@ private: labelPopulation[population.first]=population.second; } auto meanValueMap = labelStatsFilter->GetMeanValueMap(); - std::unordered_map< unsigned int, itk::VariableLengthVector<double> > meanValues; + std::unordered_map< unsigned int, itk::VariableLengthVector<double> > + meanValues; for (const auto & mean : meanValueMap) { meanValues[mean.first] = mean.second; } - // Compute the LUT from the original label image to the merged output label image. + // Compute the LUT from the original label image to the merged output + // label image. auto regionMergingFilter = LabelImageSmallRegionMergingFilterType::New(); regionMergingFilter->SetInputLabelImage( labelIn ); regionMergingFilter->SetLabelPopulation( labelPopulation ); regionMergingFilter->SetLabelStatistic( meanValues ); - regionMergingFilter->SetMinSize( minSize); AddProcess(regionMergingFilter, "Computing LUT ..."); @@ -168,7 +181,8 @@ private: SetParameterOutputImage("out", changeLabelFilter->GetOutput()); RegisterPipeline(); clock_t toc = clock(); - otbAppLogINFO(<<"Elapsed time: "<<(double)(toc - tic) / CLOCKS_PER_SEC<<" seconds"); + otbAppLogINFO(<<"Elapsed time: "<<(double)(toc - tic) / CLOCKS_PER_SEC<< + " seconds"); } }; diff --git a/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.h b/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.h index 66efb3c2cc351e44ae1f88ba179b0788cd55727e..fbfe93479119827fb6fd252b4112b6ebc00d196a 100644 --- a/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.h +++ b/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.h @@ -32,15 +32,17 @@ namespace otb /** \class PersistentLabelImageSmallRegionMergingFilter * - * This class can be used to merge each segments of a given size in a label image to - * the connected segment with the closest radiometry. - * This persistent filter should be used as template parameter of a PersistentFilterStreamingDecorator + * This class can be used to merge each segments of a given size in a label + * image to the connected segment with the closest radiometry (in the sense of + * the euclidian squared distance). + * This persistent filter should be used as template parameter of a + * PersistentFilterStreamingDecorator. * It computes from an input label image an equivalence table * that gives for each pixel, the corresponding label in the merged image. * The merged image can then be computed using a ChangeLabelImageFilter. * - * This filter can be updated several times for different values of size, the output - * equivalence table will be the results of all computations. + * This filter can be updated several times for different values of size, + * the output equivalence table will be the results of all computations. * * \ingroup ImageSegmentation * @@ -52,13 +54,14 @@ class PersistentLabelImageSmallRegionMergingFilter { public: /** Standard class typedef */ - typedef PersistentLabelImageSmallRegionMergingFilter Self; - typedef PersistentImageFilter<TInputLabelImage, TInputLabelImage> Superclass; - typedef itk::SmartPointer<Self> Pointer; - typedef itk::SmartPointer<const Self> ConstPointer; + typedef PersistentLabelImageSmallRegionMergingFilter Self; + typedef PersistentImageFilter<TInputLabelImage, TInputLabelImage> Superclass; + typedef itk::SmartPointer<Self> Pointer; + typedef itk::SmartPointer<const Self> ConstPointer; /** Type macro */ - itkTypeMacro(PersistentLabelImageSmallRegionMergingFilter, PersistentImageFilter); + itkTypeMacro(PersistentLabelImageSmallRegionMergingFilter, + PersistentImageFilter); itkNewMacro(Self); /** Template parameters typedefs */ @@ -70,13 +73,16 @@ public: typedef typename InputImageType::PointType PointType; typedef typename InputImageType::RegionType RegionType; - typedef itk::VariableLengthVector<double> RealVectorPixelType; + typedef itk::VariableLengthVector<double> RealVectorPixelType; - typedef std::unordered_map<InputLabelType, std::set<InputLabelType> > NeigboursMapType; + typedef std::unordered_map<InputLabelType, std::set<InputLabelType> > + NeigboursMapType; - typedef std::unordered_map<InputLabelType , RealVectorPixelType > LabelStatisticType; - typedef std::unordered_map<InputLabelType , double> LabelPopulationType; - typedef std::unordered_map<InputLabelType , InputLabelType> LUTType; + typedef std::unordered_map<InputLabelType , RealVectorPixelType > + LabelStatisticType; + typedef std::unordered_map<InputLabelType , double> + LabelPopulationType; + typedef std::unordered_map<InputLabelType , InputLabelType> LUTType; /** Set/Get size of segments to be merged */ itkGetMacro(Size , unsigned int); @@ -122,16 +128,18 @@ public: virtual void Synthetize(void) override; protected: - /** The input requested region should be padded by a radius of 1 to use the neigbourhood iterator*/ + /** The input requested region should be padded by a radius of 1 to use the + * neigbourhood iterator */ void GenerateInputRequestedRegion() override; - /** Threaded Generate Data : find the neighbours of each segments of size m_Size for each tile and store them in - * an accumulator */ + /** Threaded Generate Data : find the neighbours of each segments of size + * m_Size for each tile and store them in an accumulator */ void ThreadedGenerateData(const RegionType& outputRegionForThread, itk::ThreadIdType threadId) override; - // Use the LUT recursively to find the label corresponding to the input label + /** Use the LUT recursively to find the label corresponding to the input + * label */ InputLabelType FindCorrespondingLabel( InputLabelType label); /** Constructor */ @@ -150,27 +158,30 @@ private: /** Size of the segments to be merged */ unsigned int m_Size; - /** Vector containing at position i the population of the segment labelled i */ + /** Map containing at key i the population of the segment labelled i */ LabelPopulationType m_LabelPopulation; - /** Vector containing at position i the population of mean of element of the segment labelled i*/ + /** Map containing at key i the mean of element of the segment labelled i */ LabelStatisticType m_LabelStatistic; /** Neigbours maps for each thread */ std::vector <NeigboursMapType > m_NeighboursMapsTmp; - /** LUT giving correspondance between labels in the original segmentation and the merged labels */ + /** LUT giving correspondance between labels in the original segmentation + * and the merged labels */ LUTType m_LUT; }; /** \class LabelImageSmallRegionMergingFilter * - * This filter computes from a label image an equivalence table that gives for each pixel, - * the corresponding label in the merged image. - * It uses a PersistentFilterStreamingDecorator templated over a PersistentLabelImageSmallRegionMergingFilter - * to merge the segments recursively from segment of size 1 to segment of a sized specified - * by a parameter. - * The merged image can then be computed using a ChangeLabelImageFilterType. + * This filter computes from a label image an equivalence table that gives for + * each pixel, the corresponding label in the merged image. It uses a + * PersistentFilterStreamingDecorator templated over a + * PersistentLabelImageSmallRegionMergingFilter + * to merge the segments recursively from segment of size 1 to segment of a + * size specified by the attribute MinSize. + * The equivalence table can be accessed with the method GetLut and used to + * compute the merged image with a ChangeLabelImageFilterType. * * \ingroup ImageSegmentation * @@ -182,10 +193,10 @@ class ITK_EXPORT LabelImageSmallRegionMergingFilter { public: /** Standard Self typedef */ - typedef LabelImageSmallRegionMergingFilter Self; - typedef itk::ProcessObject Superclass; - typedef itk::SmartPointer<Self> Pointer; - typedef itk::SmartPointer<const Self> ConstPointer; + typedef LabelImageSmallRegionMergingFilter Self; + typedef itk::ProcessObject Superclass; + typedef itk::SmartPointer<Self> Pointer; + typedef itk::SmartPointer<const Self> ConstPointer; /** Type macro */ itkNewMacro(Self); @@ -194,12 +205,20 @@ public: itkTypeMacro(LabelImageSmallRegionMergingFilter, itk::ProcessObject); // Small region merging filter typedefs - typedef PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > PersistentLabelImageSmallRegionMergingFilterType; - typedef PersistentFilterStreamingDecorator < PersistentLabelImageSmallRegionMergingFilterType > LabelImageSmallRegionMergingFilterType; + typedef PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > + PersistentLabelImageSmallRegionMergingFilterType; + typedef PersistentFilterStreamingDecorator + < PersistentLabelImageSmallRegionMergingFilterType > + LabelImageSmallRegionMergingFilterType; - typedef typename PersistentLabelImageSmallRegionMergingFilterType::LabelPopulationType LabelPopulationType; - typedef typename PersistentLabelImageSmallRegionMergingFilterType::LabelStatisticType LabelStatisticType; - typedef typename PersistentLabelImageSmallRegionMergingFilterType::LUTType LUTType; + typedef typename PersistentLabelImageSmallRegionMergingFilterType + ::LabelPopulationType LabelPopulationType; + + typedef typename PersistentLabelImageSmallRegionMergingFilterType + ::LabelStatisticType LabelStatisticType; + + typedef typename PersistentLabelImageSmallRegionMergingFilterType::LUTType + LUTType; /** Set/Get size of polygon to be merged */ @@ -215,7 +234,8 @@ public: /** Set the Label population map */ void SetLabelPopulation( LabelPopulationType const & labelPopulation ) { - m_SmallRegionMergingFilter->GetFilter()->SetLabelPopulation( labelPopulation ); + m_SmallRegionMergingFilter->GetFilter() + ->SetLabelPopulation(labelPopulation); } /** Get the Label population map */ @@ -227,11 +247,11 @@ public: /** Set the Label statistic map */ void SetLabelStatistic( LabelStatisticType const & labelStatistic ) { - m_SmallRegionMergingFilter->GetFilter()->SetLabelStatistic( labelStatistic ); + m_SmallRegionMergingFilter->GetFilter()->SetLabelStatistic(labelStatistic); } /** Get the Label statistic map */ - LabelStatisticType const & GetLabelStatistic( ) const + LabelStatisticType const & GetLabelStatistic() const { return m_SmallRegionMergingFilter->GetFilter()->GetLabelStatistic(); } @@ -250,14 +270,19 @@ protected: /** Destructor */ ~LabelImageSmallRegionMergingFilter() override = default; - /** Generate Data method (Update LabelImageSmallRegionMergingFilterType recursively) */ + /** Generate Data method (Update LabelImageSmallRegionMergingFilterType + * recursively) */ void GenerateData() override; private: LabelImageSmallRegionMergingFilter(const Self &) = delete; void operator =(const Self&) = delete; - typename LabelImageSmallRegionMergingFilterType::Pointer m_SmallRegionMergingFilter; + // Filter used recursively to build the equivalence table + typename + LabelImageSmallRegionMergingFilterType::Pointer m_SmallRegionMergingFilter; + + // All segments with size < m_MinSize will be merged to bigger segments. unsigned int m_MinSize; }; diff --git a/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.hxx b/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.hxx index 6cabcbaf2ccca334ebd98e19fdb3896fccf27a51..57abb541ba2bbd25e172cbe0dd2af61f0c25e14f 100644 --- a/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.hxx +++ b/Modules/Segmentation/Conversion/include/otbLabelImageSmallRegionMergingFilter.hxx @@ -49,17 +49,21 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > ::Synthetize() { NeigboursMapType neighboursMap; + // Merge the neighbours maps from all threads - for( unsigned int threadId = 0; threadId < this->GetNumberOfThreads(); threadId++) + for( unsigned int threadId = 0; threadId < this->GetNumberOfThreads(); + threadId++) { for (auto const & neighbours : m_NeighboursMapsTmp[threadId]) { - neighboursMap[ neighbours.first ].insert( neighbours.second.begin(), neighbours.second.end() ); + neighboursMap[ neighbours.first ].insert + ( neighbours.second.begin(), neighbours.second.end() ); } } - // For each label of the label map, find the "closest" connected label, according - // to the euclidian distance between the corresponding m_labelStatistic elements. + // For each label of the label map, find the "closest" connected label, + // according to the euclidian distance between the corresponding + // m_labelStatistic elements. for (auto const & neighbours : neighboursMap) { double proximity = itk::NumericTraits<double>::max(); @@ -82,6 +86,8 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > auto curLabelLUT = FindCorrespondingLabel(label); auto adjLabelLUT = FindCorrespondingLabel(closestNeighbour); + // Keep the smallest label (this prevents infinite loop in the LUT + // (like LUT[i]=j and LUT[j]=i) if(curLabelLUT < adjLabelLUT) { m_LUT[adjLabelLUT] = curLabelLUT; @@ -98,24 +104,31 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > label.second = FindCorrespondingLabel( label.first ); } - // Update statistics + // Update statistics : for each newly merged segments, sum the population, and + // recompute the mean. for (auto label : m_LUT) { if((m_LabelPopulation[label.first]!=0) && (label.second != label.first)) { - m_LabelStatistic[ label.second ] = (m_LabelStatistic[label.second]*m_LabelPopulation[label.second] + - m_LabelStatistic[label.first]*m_LabelPopulation[label.first] ) / (m_LabelPopulation[label.first]+m_LabelPopulation[label.second]); + m_LabelStatistic[ label.second ] = + ( m_LabelStatistic[label.first]*m_LabelPopulation[label.first] + + m_LabelStatistic[label.second]*m_LabelPopulation[label.second] ) + / (m_LabelPopulation[label.first]+m_LabelPopulation[label.second]); + m_LabelPopulation[ label.second ] += m_LabelPopulation[ label.first ] ; + + // Do not use this label anymore m_LabelPopulation[ label.first ] = 0; } } } template <class TInputLabelImage > -typename PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage >::InputLabelType +typename PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > +::InputLabelType PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > -::FindCorrespondingLabel( typename PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > - ::InputLabelType label) +::FindCorrespondingLabel( typename PersistentLabelImageSmallRegionMergingFilter + < TInputLabelImage >::InputLabelType label) { auto correspondingLabel = m_LUT[label]; while (label != correspondingLabel) @@ -160,7 +173,8 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > // build an exception itk::InvalidRequestedRegionError e(__FILE__, __LINE__); e.SetLocation(ITK_LOCATION); - e.SetDescription("Requested region is (at least partially) outside the largest possible region."); + e.SetDescription("Requested region is (at least partially) outside the " + "largest possible region."); e.SetDataObject(inputPtr); throw e; } @@ -170,10 +184,12 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > template <class TInputLabelImage > void PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > -::ThreadedGenerateData(const RegionType& outputRegionForThread, itk::ThreadIdType threadId ) +::ThreadedGenerateData(const RegionType& outputRegionForThread, + itk::ThreadIdType threadId ) { using IteratorType = itk::ImageRegionConstIterator< TInputLabelImage >; - using NeighborhoodIteratorType = itk::ConstShapedNeighborhoodIterator< TInputLabelImage >; + using NeighborhoodIteratorType = + itk::ConstShapedNeighborhoodIterator< TInputLabelImage >; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(1); @@ -204,7 +220,7 @@ PersistentLabelImageSmallRegionMergingFilter< TInputLabelImage > { int neighbourLabel = m_LUT[ ci.Get() ]; if (neighbourLabel != currentLabel) - m_NeighboursMapsTmp[threadId][ currentLabel ].insert( neighbourLabel ); + m_NeighboursMapsTmp[threadId][ currentLabel ].insert(neighbourLabel); } } }