diff --git a/Modules/Applications/AppClassification/app/otbClassificationMapRegularization.cxx b/Modules/Applications/AppClassification/app/otbClassificationMapRegularization.cxx index 4652d5556255407d811c591fb0729add8e462b90..1219414c9821766e8204898df8edba1383b41250 100644 --- a/Modules/Applications/AppClassification/app/otbClassificationMapRegularization.cxx +++ b/Modules/Applications/AppClassification/app/otbClassificationMapRegularization.cxx @@ -99,6 +99,13 @@ private: SetParameterDescription("ip.undecidedlabel", "Label for the Undecided class. By default, 'ip.undecidedlabel = 0'."); SetDefaultParameterInt("ip.undecidedlabel", 0.0); + AddParameter(ParameterType_Empty, "ip.onlyisolatedpixels", "Process isolated pixels only"); + SetParameterDescription("ip.onlyisolatedpixels", "Only pixels whose label is unique in the neighbordhood will be processed. By default, 'ip.onlyisolatedpixels = false'."); + + AddParameter(ParameterType_Int, "ip.isolatedthreshold", "Threshold for isolated pixels"); + SetParameterDescription("ip.isolatedthreshold", "Maximum number of neighbours with the same label as the center pixel to consider that it is an isolated pixel. By default, 'ip.isolatedthreshold = 1'."); + SetDefaultParameterInt("ip.isolatedthreshold", 1); + AddRAMParameter(); @@ -107,6 +114,7 @@ private: SetDocExampleParameterValue("io.out", "clLabeledImageQB123_1_CMR_r2_nodl_10_undl_7.tif"); SetDocExampleParameterValue("ip.radius", "2"); SetDocExampleParameterValue("ip.suvbool", "true"); + SetDocExampleParameterValue("ip.onlyisolatedpixels", "true"); SetDocExampleParameterValue("ip.nodatalabel", "10"); SetDocExampleParameterValue("ip.undecidedlabel", "7"); } @@ -149,6 +157,17 @@ private: m_NeighMajVotingFilter->SetKeepOriginalLabelBool(true); } + // Process isolated pixels only + if (IsParameterEnabled("ip.onlyisolatedpixels")) + { + m_NeighMajVotingFilter->SetOnlyIsolatedPixels(true); + m_NeighMajVotingFilter->SetIsolatedThreshold(GetParameterInt("ip.isolatedthreshold")); + } + else + { + m_NeighMajVotingFilter->SetOnlyIsolatedPixels(false); + } + /** REGULARIZATION OF CLASSIFICATION */ SetParameterOutputImage<IOLabelImageType>("io.out", m_NeighMajVotingFilter->GetOutput()); diff --git a/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.h b/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.h index f7ce6b84e5e25ebbf74c5c3d1cd517dc8a43fce5..009462af47680c6516d562bc1cd3bb15eb1f2d93 100644 --- a/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.h +++ b/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.h @@ -149,6 +149,10 @@ public: //Creates a SetKeepOriginalLabelBool method itkSetMacro(KeepOriginalLabelBool, bool); + //Process only isolated pixels + itkSetMacro(OnlyIsolatedPixels, bool); + itkSetMacro(IsolatedThreshold, unsigned int); + protected: NeighborhoodMajorityVotingImageFilter(); @@ -166,6 +170,31 @@ protected: void GenerateOutputInformation() ITK_OVERRIDE; + + //Type to store the useful information from the label histogram + struct HistoSummary + { + unsigned int freqCenterLabel; + PixelType majorityLabel; + bool majorityUnique; + }; + + struct CompareHistoFequencies + { + typedef std::pair<PixelType, unsigned int> HistoValueType; + bool operator()(const HistoValueType& a, const HistoValueType& b) + { + return a.second > b.second; + } + }; + + //Get a histogram of frequencies of labels with the 2 highest + // frequencies sorted in decreasing order and return the frequency + // of the label of the center pixel + const HistoSummary ComputeNeighborhoodHistogramSummary(const NeighborhoodIteratorType &nit, + const KernelIteratorType kernelBegin, + const KernelIteratorType kernelEnd) const; + private: NeighborhoodMajorityVotingImageFilter(const Self&); //purposely not implemented void operator=(const Self&); //purposely not implemented @@ -177,6 +206,12 @@ private: PixelType m_LabelForUndecidedPixels; bool m_KeepOriginalLabelBool; + //Choose to filter only isolated pixels + bool m_OnlyIsolatedPixels; + //The center pixel is isolated if there are fewer neighbours than + //this threshold with the same label + unsigned int m_IsolatedThreshold; + }; // end of class } // end namespace otb diff --git a/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.txx b/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.txx index 5fab37c3fe7a31395541cf91684ec5ef78419835..9c4de263463dfb2ace173423bc4bc19dd5c05253 100644 --- a/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.txx +++ b/Modules/Fusion/MajorityVoting/include/otbNeighborhoodMajorityVotingImageFilter.txx @@ -38,87 +38,93 @@ NeighborhoodMajorityVotingImageFilter<TInputImage, TOutputImage, TKernel>::Neigh this->SetLabelForNoDataPixels(itk::NumericTraits<PixelType>::NonpositiveMin()); //m_LabelForNoDataPixels = 0 this->SetLabelForUndecidedPixels(itk::NumericTraits<PixelType>::NonpositiveMin()); //m_LabelForUndecidedPixels = 0 this->SetKeepOriginalLabelBool(true); //m_KeepOriginalLabelBool = true + this->SetOnlyIsolatedPixels(false); //process all pixels + this->SetIsolatedThreshold(1); } template<class TInputImage, class TOutputImage, class TKernel> typename NeighborhoodMajorityVotingImageFilter<TInputImage, TOutputImage, -TKernel>::PixelType NeighborhoodMajorityVotingImageFilter<TInputImage, -TOutputImage, TKernel>::Evaluate(const NeighborhoodIteratorType &nit, -const KernelIteratorType kernelBegin, -const KernelIteratorType kernelEnd) + TKernel>::PixelType +NeighborhoodMajorityVotingImageFilter<TInputImage, + TOutputImage, TKernel>::Evaluate(const NeighborhoodIteratorType &nit, + const KernelIteratorType kernelBegin, + const KernelIteratorType kernelEnd) { - PixelType majorityLabel = 0; //Value of the more representative pixels in the neighborhood - unsigned int majorityFreq = 0; //Number of pixels with the more representative value (majorityLabel) in the neighborhood - - unsigned int i; - KernelIteratorType kernel_it; - - std::map<PixelType, unsigned int> histoNeigh; - - PixelType centerPixel = nit.GetCenterPixel(); - - if (centerPixel != m_LabelForNoDataPixels) - { - for (i = 0, kernel_it = kernelBegin; kernel_it < kernelEnd; ++kernel_it, ++i) + const PixelType centerPixel = nit.GetCenterPixel(); + if (centerPixel == m_LabelForNoDataPixels) { - // if structuring element is positive, use the pixel under that element - // in the image - - PixelType label = nit.GetPixel(i); - if ((*kernel_it > itk::NumericTraits<KernelPixelType>::Zero) && (label != m_LabelForNoDataPixels)) + return m_LabelForNoDataPixels; + } + else + { + //Get a histogram of label frequencies where the 2 highest are at the beginning and sorted + const HistoSummary histoSummary = + this->ComputeNeighborhoodHistogramSummary(nit, kernelBegin, kernelEnd); + if(m_OnlyIsolatedPixels && + histoSummary.freqCenterLabel > m_IsolatedThreshold) { - // note we use GetPixel() on the SmartNeighborhoodIterator to - // respect boundary conditions - - //If the current label has already been added to the histogram histoNeigh - if (histoNeigh.count(label) > 0) + //If we want to filter only isolated pixels, keep the label if + //there are enough pixels with the center label to consider that + //it is not isolated + return centerPixel; + } + else + { + //If the majorityLabel is NOT unique in the neighborhood + if(!histoSummary.majorityUnique) { - histoNeigh[label]++; - } + if (m_KeepOriginalLabelBool == true) + { + return centerPixel; + } else - { - histoNeigh[label] = 1; + { + return m_LabelForUndecidedPixels; + } } + //Extraction of the more representative Label in the neighborhood (majorityLabel) + return histoSummary.majorityLabel; } - } - - //Extraction of the more representative Label in the neighborhood (majorityLabel) and its Frequency (majorityFreq) - typename std::map<PixelType, unsigned int>::iterator histoIt; - for (histoIt = histoNeigh.begin(); histoIt != histoNeigh.end(); ++histoIt) - { - if (histoIt->second > majorityFreq) - { - majorityFreq = histoIt->second; //Frequency - majorityLabel = histoIt->first; //Label - } - } + }//END if (centerPixel != m_LabelForNoDataPixels) +} - //If the majorityLabel is NOT unique in the neighborhood - for (histoIt = histoNeigh.begin(); histoIt != histoNeigh.end(); ++histoIt) +template<class TInputImage, class TOutputImage, class TKernel> +const typename NeighborhoodMajorityVotingImageFilter<TInputImage, TOutputImage, + TKernel>::HistoSummary +NeighborhoodMajorityVotingImageFilter<TInputImage, TOutputImage, + TKernel>::ComputeNeighborhoodHistogramSummary(const NeighborhoodIteratorType &nit, + const KernelIteratorType kernelBegin, + const KernelIteratorType kernelEnd) const +{ + typedef std::map<PixelType, unsigned int> HistogramType; + typedef std::vector<std::pair<PixelType, unsigned int> > HistoAsVectorType; + HistogramType histoNeigh; + unsigned int i = 0; + for (KernelIteratorType kernel_it = kernelBegin; + kernel_it < kernelEnd; ++kernel_it, ++i) { - if ( (histoIt->second == majorityFreq) && (histoIt->first != majorityLabel) ) + // if structuring element is positive, use the pixel under that element + // in the image + // note we use GetPixel() on the SmartNeighborhoodIterator to + // respect boundary conditions + const PixelType label = nit.GetPixel(i); + if ((*kernel_it > itk::NumericTraits<KernelPixelType>::Zero) && + (label != m_LabelForNoDataPixels)) { - if (m_KeepOriginalLabelBool == true) - { - majorityLabel = centerPixel; - } - else - { - majorityLabel = m_LabelForUndecidedPixels; - } - break; + histoNeigh[label] += 1; } } - }//END if (centerPixel != m_LabelForNoDataPixels) - - //If (centerPixel == m_LabelForNoDataPixels) - else - { - majorityLabel = m_LabelForNoDataPixels; - } - - return majorityLabel; + HistoAsVectorType histoNeighVec(histoNeigh.begin(), histoNeigh.end()); + //Sort the 2 max elements to the beginning + std::nth_element(histoNeighVec.begin(), histoNeighVec.begin()+1, + histoNeighVec.end(), CompareHistoFequencies()); + typename NeighborhoodMajorityVotingImageFilter<TInputImage, TOutputImage, + TKernel>::HistoSummary result; + result.freqCenterLabel = histoNeigh[nit.GetCenterPixel()]; + result.majorityLabel = histoNeighVec[0].first; + result.majorityUnique = (histoNeighVec[0].second != histoNeighVec[1].second); + return result; } template<class TInputImage, class TOutputImage, class TKernel> diff --git a/Modules/Fusion/MajorityVoting/test/CMakeLists.txt b/Modules/Fusion/MajorityVoting/test/CMakeLists.txt index a32c7664d64ed6f6a7b5134697852a1f6a5d56b4..5547d89d23e1b9aaaf172d0fb48433b301b284e4 100644 --- a/Modules/Fusion/MajorityVoting/test/CMakeLists.txt +++ b/Modules/Fusion/MajorityVoting/test/CMakeLists.txt @@ -27,6 +27,11 @@ otb_add_test(NAME leTvNeighborhoodMajorityVotingImageFilterTest COMMAND otbMajor 5 #yRadius 10 #LabelForNoDataPixels 7 #LabelForUndecidedPixels + 0 #OnlyIsolatedPixels + ) + +otb_add_test(NAME leTvNeighborhoodMajorityVotingIsolThresPixTest COMMAND otbMajorityVotingTestDriver + otbNeighborhoodMajorityVotingImageFilterIsolatedTest ) otb_add_test(NAME leTvSVMImageClassificationFilterWithNeighborhoodMajorityVoting COMMAND otbMajorityVotingTestDriver diff --git a/Modules/Fusion/MajorityVoting/test/otbMajorityVotingTestDriver.cxx b/Modules/Fusion/MajorityVoting/test/otbMajorityVotingTestDriver.cxx index 3a961ec71e79efcfcd093dd8b4d72c45a08b3552..a3a3809011a04089e7ba5efff515975ae4fd416f 100644 --- a/Modules/Fusion/MajorityVoting/test/otbMajorityVotingTestDriver.cxx +++ b/Modules/Fusion/MajorityVoting/test/otbMajorityVotingTestDriver.cxx @@ -3,4 +3,5 @@ void RegisterTests() { REGISTER_TEST(otbNeighborhoodMajorityVotingImageFilterNew); REGISTER_TEST(otbNeighborhoodMajorityVotingImageFilterTest); + REGISTER_TEST(otbNeighborhoodMajorityVotingImageFilterIsolatedTest); } diff --git a/Modules/Fusion/MajorityVoting/test/otbNeighborhoodMajorityVotingImageFilterTest.cxx b/Modules/Fusion/MajorityVoting/test/otbNeighborhoodMajorityVotingImageFilterTest.cxx index b6a0e1e92376c1889f3acee7693618dcc12d78eb..9a9de6918b9d61e3b1935455c210374e0d1a6d77 100644 --- a/Modules/Fusion/MajorityVoting/test/otbNeighborhoodMajorityVotingImageFilterTest.cxx +++ b/Modules/Fusion/MajorityVoting/test/otbNeighborhoodMajorityVotingImageFilterTest.cxx @@ -100,6 +100,11 @@ int otbNeighborhoodMajorityVotingImageFilterTest(int argc, char* argv[]) seBall.CreateStructuringElement(); NeighMajVotingFilter->SetKernel(seBall); + if(argc==9) + { + NeighMajVotingFilter->SetOnlyIsolatedPixels(static_cast<bool>(atoi(argv[8]))); + } + WriterType::Pointer writer = WriterType::New(); writer->SetFileName(outputFileName); writer->SetInput(NeighMajVotingFilter->GetOutput()); @@ -107,3 +112,96 @@ int otbNeighborhoodMajorityVotingImageFilterTest(int argc, char* argv[]) return EXIT_SUCCESS; } + +int otbNeighborhoodMajorityVotingImageFilterIsolatedTest(int itkNotUsed(argc), char* itkNotUsed(argv)[]) +{ + typedef unsigned char PixelType; // 8 bits + const unsigned int Dimension = 2; + + typedef otb::Image<PixelType, Dimension> ImageType; + + ImageType::Pointer image = ImageType::New(); + ImageType::IndexType start; + + start[0] = 0; + start[1] = 0; + + ImageType::SizeType size; + size[0] = 100; + size[1] = 100; + + ImageType::RegionType region; + + region.SetSize(size); + region.SetIndex(start); + image->SetRegions(region); + image->Allocate(); + image->FillBuffer(itk::NumericTraits<PixelType>::Zero); + + // Neighborhood majority voting filter type + typedef otb::NeighborhoodMajorityVotingImageFilter<ImageType> NeighborhoodMajorityVotingFilterType; + + // Binary ball Structuring Element type + typedef NeighborhoodMajorityVotingFilterType::KernelType StructuringType; + typedef StructuringType::RadiusType RadiusType; + + + // Neighborhood majority voting filter + NeighborhoodMajorityVotingFilterType::Pointer NeighMajVotingFilter; + NeighMajVotingFilter = NeighborhoodMajorityVotingFilterType::New(); + + NeighMajVotingFilter->SetInput(image); + + StructuringType seBall; + RadiusType rad; + + + NeighMajVotingFilter->SetKeepOriginalLabelBool(true); + + rad[0] = 1; + rad[1] = 1; + NeighMajVotingFilter->SetLabelForNoDataPixels(10); + NeighMajVotingFilter->SetLabelForUndecidedPixels(7); + seBall.SetRadius(rad); + seBall.CreateStructuringElement(); + NeighMajVotingFilter->SetKernel(seBall); + NeighMajVotingFilter->SetOnlyIsolatedPixels(true); + PixelType value = 255; + ImageType::IndexType coordinate; + coordinate[0] = 10; + coordinate[1] = 10; + image->SetPixel(coordinate, value); + image->Update(); + NeighMajVotingFilter->SetIsolatedThreshold(1); + NeighMajVotingFilter->Update(); + PixelType result = NeighMajVotingFilter->GetOutput()->GetPixel(coordinate); +//Should be filtered + if(result == value) + { + std::cout << "one pixel\n"; + return EXIT_FAILURE; + } + coordinate[0] = 10; + coordinate[1] = 11; + image->SetPixel(coordinate, value); + image->Update(); + NeighMajVotingFilter->Modified(); //needed for the filter to be updated + NeighMajVotingFilter->Update(); + result = NeighMajVotingFilter->GetOutput()->GetPixel(coordinate); + // Should not be filtered + if( result != value) + { + std::cout << "2 pixels thres = 1 result = " << int(result) << '\n'; + return EXIT_FAILURE; + } + NeighMajVotingFilter->SetIsolatedThreshold(3); + NeighMajVotingFilter->Update(); + result = NeighMajVotingFilter->GetOutput()->GetPixel(coordinate); +//Should be filtered + if(result == value) + { + std::cout << "2 pixels thres = 2" << '\n'; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +}