diff --git a/Applications/Classification/otbSOMClassification.cxx b/Applications/Classification/otbSOMClassification.cxx index 7d6edbfdf63451001ecba1bf35ff4472f465ee33..aa2307378f0974edfdc8b1e0855c7973a0a5474b 100644 --- a/Applications/Classification/otbSOMClassification.cxx +++ b/Applications/Classification/otbSOMClassification.cxx @@ -1,301 +1,345 @@ -#include "otbVectorImage.h" -#include "otbImage.h" -#include "otbImageFileReader.h" -#include "otbStreamingImageFileWriter.h" -#include "otbImageFileWriter.h" +/*========================================================================= + + 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 "otbWrapperApplication.h" +#include "otbWrapperApplicationFactory.h" + #include "otbSOMMap.h" #include "otbSOM.h" #include "otbSOMImageClassificationFilter.h" -#include "otbCommandLineArgumentParser.h" #include "itkEuclideanDistance.h" #include "itkImageRegionSplitter.h" #include "otbStreamingTraits.h" #include "itkImageRegionConstIterator.h" -#include "itkVariableSizeMatrix.h" -#include "itkListSample.h" -#include "itkVariableLengthVector.h" +// #include "itkListSample.h" +#include "itkImageRandomNonRepeatingConstIteratorWithIndex.h" +//#include "itkMersenneTwisterRandomVariateGenerator.h" -int main(int argc, char * argv[]) +namespace otb +{ +namespace Wrapper { - // Parse command line parameters - typedef otb::CommandLineArgumentParser ParserType; - ParserType::Pointer parser = ParserType::New(); - - parser->SetProgramDescription("Unsupervised Self Organizing Map image classification"); - parser->AddInputImage(); - parser->AddOutputImage(); - parser->AddOption("--ValidityMask","Validity mask","-vm", 1, true); - parser->AddOption("--MaxTrainingSetSize","Size of the training set","-ts", 1, true); - parser->AddOption("--TrainingSetProbability","Probability for a sample to be selected in the training set","-tp", 1, true); - parser->AddOption("--StreamingNumberOfLines","Number of lined for each streaming block","-sl", 1, true); - parser->AddOption("--SOMMap","Output SOM map","-sm", 1, true); - parser->AddOption("--SizeX","X size of the SOM map","-sx", 1, true); - parser->AddOption("--SizeY","Y size of the SOM map","-sy", 1, true); - parser->AddOption("--NeighborhoodInitX","X initial neighborhood of the SOM map","-nx", 1, true); - parser->AddOption("--NeighborhoodInitY","Y initial neighborhood of the SOM map","-ny", 1, true); - parser->AddOption("--NumberOfIterations","Number of iterations of the SOM learning","-ni", 1, true); - parser->AddOption("--BetaInit","Initial beta value","-bi", 1, true); - parser->AddOption("--BetaFinal","Final beta value","-be", 1, true); - parser->AddOption("--InitValue","Initial value","-iv", 1, true); - - - typedef otb::CommandLineArgumentParseResult ParserResultType; - ParserResultType::Pointer parseResult = ParserResultType::New(); - - try - { - parser->ParseCommandLine(argc, argv, parseResult); - } - catch ( itk::ExceptionObject & err ) - { - std::string descriptionException = err.GetDescription(); - if (descriptionException.find("ParseCommandLine(): Help Parser") != std::string::npos) - { - return EXIT_SUCCESS; - } - if (descriptionException.find("ParseCommandLine(): Version Parser") != std::string::npos) - { - return EXIT_SUCCESS; - } - return EXIT_FAILURE; - } - - // initiating random number generation - srand(time(NULL)); +class SOMClassification : public Application +{ +public: + /** Standard class typedefs. */ + typedef SOMClassification Self; + typedef Application Superclass; + typedef itk::SmartPointer<Self> Pointer; + typedef itk::SmartPointer<const Self> ConstPointer; - std::string infname = parseResult->GetInputImage(); - std::string maskfname = parseResult->GetParameterString("--ValidityMask", 0); - std::string outfname = parseResult->GetOutputImage(); - std::string somfname = parseResult->GetParameterString("--SOMMap", 0); - const unsigned int nbsamples = parseResult->GetParameterUInt("--MaxTrainingSetSize"); - const double trainingProb =parseResult->GetParameterDouble("--TrainingSetProbability"); - const unsigned int nbLinesForStreaming = parseResult->GetParameterUInt("--StreamingNumberOfLines"); - const unsigned int sizeX = parseResult->GetParameterUInt("--SizeX"); - const unsigned int sizeY = parseResult->GetParameterUInt("--SizeY"); - const unsigned int neighInitX = parseResult->GetParameterUInt("--NeighborhoodInitX"); - const unsigned int neighInitY= parseResult->GetParameterUInt("--NeighborhoodInitY"); - const unsigned int nbIterations=parseResult->GetParameterUInt("--NumberOfIterations"); - const double betaInit = parseResult->GetParameterDouble("--BetaInit"); - const double betaEnd= parseResult->GetParameterDouble("--BetaFinal"); - const float initValue = parseResult->GetParameterFloat("--InitValue"); + /** Standard macro */ + itkNewMacro(Self); - typedef float PixelType; - typedef unsigned short LabeledPixelType; + itkTypeMacro(SOMClassification, otb::Application); - typedef otb::VectorImage<PixelType, 2> ImageType; - typedef otb::Image<LabeledPixelType, 2> LabeledImageType; - typedef otb::ImageFileReader<ImageType> ImageReaderType; - typedef otb::ImageFileReader<LabeledImageType> LabeledImageReaderType; - typedef otb::StreamingImageFileWriter<LabeledImageType> WriterType; + /** Filters typedef */ + typedef UInt16ImageType LabeledImageType; typedef itk::VariableLengthVector<double> SampleType; typedef itk::Statistics::EuclideanDistance<SampleType> DistanceType; typedef otb::SOMMap<SampleType, DistanceType, 2> SOMMapType; typedef itk::Statistics::ListSample<SampleType> ListSampleType; typedef otb::SOM<ListSampleType, SOMMapType> EstimatorType; - typedef otb::ImageFileWriter<ImageType> SOMMapWriterType; - typedef otb::StreamingTraits<ImageType> StreamingTraitsType; + typedef otb::StreamingTraits<FloatVectorImageType> StreamingTraitsType; typedef itk::ImageRegionSplitter<2> SplitterType; - typedef ImageType::RegionType RegionType; + typedef FloatVectorImageType::RegionType RegionType; - typedef itk::ImageRegionConstIterator<ImageType> IteratorType; + typedef itk::ImageRegionConstIterator<FloatVectorImageType> IteratorType; typedef itk::ImageRegionConstIterator<LabeledImageType> LabeledIteratorType; typedef itk::ImageRegionConstIterator<SOMMapType> SOMIteratorType; - typedef otb::SOMImageClassificationFilter<ImageType, LabeledImageType, SOMMapType> ClassificationFilterType; - - - ImageReaderType::Pointer reader = ImageReaderType::New(); - LabeledImageReaderType::Pointer maskReader = LabeledImageReaderType::New(); - - reader->SetFileName(infname); - maskReader->SetFileName(maskfname); - - /*******************************************/ - /* Sampling data */ - /*******************************************/ - std::cout<<std::endl; - std::cout<<"-- SAMPLING DATA --"<<std::endl; - std::cout<<std::endl; + typedef otb::SOMImageClassificationFilter + <FloatVectorImageType, LabeledImageType, SOMMapType> ClassificationFilterType; - // Update input images information - reader->GenerateOutputInformation(); - maskReader->GenerateOutputInformation(); - - if (reader->GetOutput()->GetLargestPossibleRegion() - != maskReader->GetOutput()->GetLargestPossibleRegion() - ) + +private: + SOMClassification() { - std::cerr<<"Mask image and input image have different sizes."<<std::endl; - return EXIT_FAILURE; + SetName("SOMClassification"); + SetDescription("SOM image classification."); + + // Documentation + SetDocName("SOM Classification Application"); + SetDocLongDescription("Unsupervised Self Organizing Map image classification."); + SetDocLimitations("None"); + SetDocAuthors("OTB-Team"); + SetDocSeeAlso(" "); + + AddDocTag(Tags::Segmentation); + AddDocTag(Tags::Learning); } - RegionType largestRegion = reader->GetOutput()->GetLargestPossibleRegion(); - - // Setting up local streaming capabilities - SplitterType::Pointer splitter = SplitterType::New(); - unsigned int numberOfStreamDivisions = StreamingTraitsType::CalculateNumberOfStreamDivisions(reader->GetOutput(), - largestRegion, - splitter, - otb::SET_BUFFER_NUMBER_OF_LINES, - 0, 0, nbLinesForStreaming); - - std::cout<<"The images will be streamed into "<<numberOfStreamDivisions<<" parts."<<std::endl; - - // Training sample lists - ListSampleType::Pointer sampleList = ListSampleType::New(); - - // Sample dimension and max dimension - unsigned int sampleSize = reader->GetOutput()->GetNumberOfComponentsPerPixel(); - unsigned int totalSamples = 0; - std::cout<<"The following sample size will be used: "<<sampleSize<<std::endl; - std::cout<<std::endl; - // local streaming variables - unsigned int piece = 0; - RegionType streamingRegion; - - while (totalSamples<nbsamples) + virtual ~SOMClassification() { - piece = static_cast<unsigned int>(static_cast<double>(numberOfStreamDivisions)*rand()/(RAND_MAX)); - - streamingRegion = splitter->GetSplit(piece, numberOfStreamDivisions, largestRegion); - - std::cout<<"Processing region: "<<streamingRegion<<std::endl; - - reader->GetOutput()->SetRequestedRegion(streamingRegion); - reader->GetOutput()->PropagateRequestedRegion(); - reader->GetOutput()->UpdateOutputData(); - - maskReader->GetOutput()->SetRequestedRegion(streamingRegion); - maskReader->GetOutput()->PropagateRequestedRegion(); - maskReader->GetOutput()->UpdateOutputData(); - - IteratorType it(reader->GetOutput(), streamingRegion); - LabeledIteratorType maskIt(maskReader->GetOutput(), streamingRegion); - - it.GoToBegin(); - maskIt.GoToBegin(); - - unsigned int localNbSamples=0; + } - // Loop on the image - while (!it.IsAtEnd()&&!maskIt.IsAtEnd()&&(totalSamples<nbsamples)) + void DoCreateParameters() + { + AddParameter(ParameterType_InputImage, "in", "InputImage"); + SetParameterDescription("in", "Input image."); + + AddParameter(ParameterType_OutputImage, "out", "OutputImage"); + SetParameterDescription("out", "Output classified image."); + + AddParameter(ParameterType_InputImage, "vm", "ValidityMask"); + SetParameterDescription("vm", "Validity mask"); + + AddParameter(ParameterType_Float, "tp", "TrainingProbability"); + SetParameterDescription("tp", "Probability for a sample to be selected in the training set"); + + AddParameter(ParameterType_Int, "ts", "TrainingSet"); + SetParameterDescription("ts", "Maximum training set size"); + + AddParameter(ParameterType_Int, "sl", "StreamingLines"); + SetParameterDescription("sl", "Number of lines in each streaming block (used during data sampling)"); + + AddParameter(ParameterType_OutputImage, "som", "SOM Map"); + SetParameterDescription("som","Self-Organizing Map map"); + + AddParameter(ParameterType_Int, "sx", "SizeX"); + SetParameterDescription("sx", "X size of the SOM map"); + + AddParameter(ParameterType_Int, "sy", "SizeY"); + SetParameterDescription("sy", "Y size of the SOM map"); + + AddParameter(ParameterType_Int, "nx", "NeighborhoodX"); + SetParameterDescription("nx", "X initial neighborhood of the SOM map"); + + AddParameter(ParameterType_Int, "ny", "NeighborhoodY"); + SetParameterDescription("ny", "Y initial neighborhood of the SOM map"); + + AddParameter(ParameterType_Int, "ni", "NumberIteration"); + SetParameterDescription("ni", "Number of iterations of the SOM learning"); + + AddParameter(ParameterType_Float, "bi", "BetaInit"); + SetParameterDescription("bi", "Initial beta value"); + + AddParameter(ParameterType_Float, "bf", "BetaFinal"); + SetParameterDescription("bf", "Final beta value"); + + AddParameter(ParameterType_Float, "iv", "InitialValue"); + SetParameterDescription("iv", "Initial value (max weight)"); + + + AddParameter(ParameterType_RAM, "ram", "Available RAM"); + SetDefaultParameterInt("ram", 256); + MandatoryOff("ram"); + // TODO : replace StreamingLines by RAM param ? + + // Default parameters + SetDefaultParameterInt("sx", 32); + SetDefaultParameterInt("sy", 32); + SetDefaultParameterInt("nx", 10); + SetDefaultParameterInt("ny", 10); + SetDefaultParameterInt("ni", 5); + SetDefaultParameterFloat("bi",1.0); + SetDefaultParameterFloat("bf",0.1); + SetDefaultParameterFloat("iv",0.0); + + // Doc example parameter settings + SetDocExampleParameterValue("in", "poupees_sub.png"); + SetDocExampleParameterValue("out","poupees_classif.tif"); + SetDocExampleParameterValue("vm", "BASELINE/leSOMPoupeesClassified.hdr"); + SetDocExampleParameterValue("tp", "1.0"); + SetDocExampleParameterValue("ts","16384"); + SetDocExampleParameterValue("sl", "32"); + SetDocExampleParameterValue("som", "poupees_map.hdr"); + SetDocExampleParameterValue("sx", "32"); + SetDocExampleParameterValue("sy", "32"); + SetDocExampleParameterValue("nx", "10"); + SetDocExampleParameterValue("ny", "10"); + SetDocExampleParameterValue("ni", "5"); + SetDocExampleParameterValue("bi", "1.0"); + SetDocExampleParameterValue("bf", "0.1"); + SetDocExampleParameterValue("iv", "0"); + } + + void DoUpdateParameters() + { + // Nothing to do + } + + void DoExecute() { - // If the current pixel is labeled - if (maskIt.Get()>0) + // initiating random number generation + itk::Statistics::MersenneTwisterRandomVariateGenerator::Pointer + randomGen = itk::Statistics::MersenneTwisterRandomVariateGenerator::New(); + + FloatVectorImageType::Pointer input = GetParameterImage("in"); + LabeledImageType::Pointer mask = GetParameterImage<LabeledImageType>("vm"); + + /*******************************************/ + /* Sampling data */ + /*******************************************/ + otbAppLogINFO("-- SAMPLING DATA --"); + + if (input->GetLargestPossibleRegion() + != mask->GetLargestPossibleRegion()) + { + otbAppLogFATAL("Mask image and input image have different sizes."); + } + + RegionType largestRegion = input->GetLargestPossibleRegion(); + + // Setting up local streaming capabilities + SplitterType::Pointer splitter = SplitterType::New(); + unsigned int numberOfStreamDivisions = StreamingTraitsType::CalculateNumberOfStreamDivisions(input, + largestRegion, + splitter, + otb::SET_BUFFER_NUMBER_OF_LINES, + 0, 0, GetParameterInt("sl")); + + otbAppLogINFO("The images will be streamed into "<<numberOfStreamDivisions<<" parts."); + + // Training sample lists + ListSampleType::Pointer sampleList = ListSampleType::New(); + const double trainingProb = static_cast<double>(GetParameterFloat("tp")); + const unsigned int nbsamples = GetParameterInt("ts"); + + // Sample dimension and max dimension + unsigned int sampleSize = input->GetNumberOfComponentsPerPixel(); + unsigned int totalSamples = 0; + otbAppLogINFO("The following sample size will be used: "<<sampleSize); + + // local streaming variables + unsigned int piece = 0; + RegionType streamingRegion; + + // create a random permutation to explore + itk::RandomPermutation randPerm(numberOfStreamDivisions); + unsigned int index = 0; + + // TODO : maybe change the approach: at the moment, the sampling process is able to pick a sample twice or more + while (totalSamples < nbsamples) + { + piece = randPerm[index]; + + streamingRegion = splitter->GetSplit(piece, numberOfStreamDivisions, largestRegion); + + //otbAppLogINFO("Processing region: "<<streamingRegion); + + input->SetRequestedRegion(streamingRegion); + input->PropagateRequestedRegion(); + input->UpdateOutputData(); + + mask->SetRequestedRegion(streamingRegion); + mask->PropagateRequestedRegion(); + mask->UpdateOutputData(); + + IteratorType it(input, streamingRegion); + LabeledIteratorType maskIt(mask, streamingRegion); + + it.GoToBegin(); + maskIt.GoToBegin(); + + unsigned int localNbSamples=0; + + // Loop on the image + while ( !it.IsAtEnd() + && !maskIt.IsAtEnd() + && (totalSamples<nbsamples)) { - if ((rand()<trainingProb*RAND_MAX)) + // If the current pixel is labeled + if (maskIt.Get()>0) { - SampleType newSample; - newSample.SetSize(sampleSize); - // build the sample - newSample.Fill(0); - for (unsigned int i = 0; i<sampleSize; ++i) + if (randomGen->GetVariateWithClosedRange() < trainingProb) { - newSample[i]=it.Get()[i]; + SampleType newSample; + newSample.SetSize(sampleSize); + // build the sample + newSample.Fill(0); + for (unsigned int i = 0; i<sampleSize; ++i) + { + newSample[i]=it.Get()[i]; + } + // Update the sample lists + sampleList->PushBack(newSample); + ++totalSamples; + ++localNbSamples; } - // Update the the sample lists - sampleList->PushBack(newSample); - ++totalSamples; - ++localNbSamples; } + ++it; + ++maskIt; } - ++it; - ++maskIt; + + index++; + // we could break out of the while loop here, once the entire image has been streamed once + if (index == numberOfStreamDivisions) index = 0; + + //otbAppLogINFO(""<<localNbSamples<<" samples added to the training set."); } - std::cout<<localNbSamples<<" samples added to the training set."<<std::endl; - std::cout<<std::endl; - } - - std::cout<<"The final training set contains "<<totalSamples<<" samples."<<std::endl; - - - std::cout<<std::endl; - std::cout<<"Data sampling completed."<<std::endl; - std::cout<<std::endl; - - /*******************************************/ - /* Learning */ - /*******************************************/ - - std::cout<<"-- LEARNING --"<<std::endl; - - EstimatorType::Pointer estimator = EstimatorType::New(); - - estimator->SetListSample(sampleList); - EstimatorType::SizeType size; - size[0]=sizeX; - size[1]=sizeY; - estimator->SetMapSize(size); - EstimatorType::SizeType radius; - radius[0] = neighInitX; - radius[1] = neighInitY; - estimator->SetNeighborhoodSizeInit(radius); - estimator->SetNumberOfIterations(nbIterations); - estimator->SetBetaInit(betaInit); - estimator->SetBetaEnd(betaEnd); - estimator->SetMaxWeight(initValue); -// estimator->SetRandomInit(true); -// estimator->SetSeed(time(NULL)); - estimator->Update(); - - ImageType::Pointer vectormap = ImageType::New(); - vectormap->SetRegions(estimator->GetOutput()->GetLargestPossibleRegion()); - vectormap->SetNumberOfComponentsPerPixel(108); - vectormap->Allocate(); - - ImageType::PixelType black; - black.SetSize(108); - black.Fill(0); - vectormap->FillBuffer(black); - - SOMIteratorType somIt(estimator->GetOutput(), estimator->GetOutput()->GetLargestPossibleRegion()); - IteratorType vectorIt(vectormap, estimator->GetOutput()->GetLargestPossibleRegion()); - - somIt.GoToBegin(); - vectorIt.GoToBegin(); - - while (!somIt.IsAtEnd() && !vectorIt.IsAtEnd()) - { - for (unsigned int i = 0; i<somIt.Get().GetSize(); ++i) - { - vectorIt.Get()[i]=somIt.Get()[i]; + + otbAppLogINFO("The final training set contains "<<totalSamples<<" samples."); + + /*******************************************/ + /* Learning */ + /*******************************************/ + otbAppLogINFO("-- LEARNING --"); + + EstimatorType::Pointer estimator = EstimatorType::New(); + + estimator->SetListSample(sampleList); + EstimatorType::SizeType size; + size[0]=GetParameterInt("sx"); + size[1]=GetParameterInt("sy"); + estimator->SetMapSize(size); + EstimatorType::SizeType radius; + radius[0] = GetParameterInt("nx"); + radius[1] = GetParameterInt("ny"); + estimator->SetNeighborhoodSizeInit(radius); + estimator->SetNumberOfIterations(GetParameterInt("ni")); + estimator->SetBetaInit(GetParameterFloat("bi")); + estimator->SetBetaEnd(GetParameterFloat("bf")); + estimator->SetMaxWeight(GetParameterFloat("iv")); + + AddProcess(estimator,"Learning"); + estimator->Update(); + + m_SOMMap = estimator->GetOutput(); + SetParameterOutputImage<DoubleVectorImageType>("som",m_SOMMap); + + /*******************************************/ + /* Classification */ + /*******************************************/ + otbAppLogINFO("-- CLASSIFICATION --"); + + m_Classifier = ClassificationFilterType::New(); + m_Classifier->SetInput(input); + m_Classifier->SetInputMask(mask); + m_Classifier->SetMap(m_SOMMap); + + AddProcess(m_Classifier,"Classification"); + + SetParameterOutputImage<LabeledImageType>("out",m_Classifier->GetOutput()); } - ++somIt; - ++vectorIt; - } - - SOMMapWriterType::Pointer somWriter = SOMMapWriterType::New(); - somWriter->SetFileName(somfname); - somWriter->SetInput(vectormap); - somWriter->Update(); - - std::cout<<std::endl; - std::cout<<"Learning completed."<<std::endl; - std::cout<<std::endl; - - /*******************************************/ - /* Classification */ - /*******************************************/ + SOMMapType::Pointer m_SOMMap; + ClassificationFilterType::Pointer m_Classifier; +}; - std::cout<<"-- CLASSIFICATION --"<<std::endl; - std::cout<<std::endl; - ClassificationFilterType::Pointer classifier = ClassificationFilterType::New(); - classifier->SetInput(reader->GetOutput()); - classifier->SetInputMask(maskReader->GetOutput()); - classifier->SetMap(estimator->GetOutput()); +} +} - WriterType::Pointer writer = WriterType::New(); - writer->SetFileName(outfname); - writer->SetInput(classifier->GetOutput()); - writer->SetNumberOfDivisionsStrippedStreaming(numberOfStreamDivisions); - writer->Update(); +OTB_APPLICATION_EXPORT(otb::Wrapper::SOMClassification) - std::cout<<"Classification completed."<<std::endl; - std::cout<<std::endl; +int main(int argc, char * argv[]) +{ - return EXIT_SUCCESS; }