DempsterShaferFusionOfClassificationMapsExample.cxx 11.8 KB
Newer Older
1
/*
Julien Michel's avatar
Julien Michel committed
2
 * Copyright (C) 2005-2019 Centre National d'Etudes Spatiales (CNES)
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * 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.
 */
20 21 22


// The fusion filter \doxygen{otb}{DSFusionOfClassifiersImageFilter} is based on the Dempster
23 24
// Shafer (DS) fusion framework. For each pixel, it chooses the class label \emph{Ai} for which the
// belief function \emph{bel(Ai)} is maximal after the DS combination of all the available masses of
25 26 27
// belief of all the class labels. The masses of belief (MOBs) of all the labels present in each
// classification map are read from input *.CSV confusion matrix files.
// Moreover, the pixels into the input classification maps to be fused which are equal to the
28 29
// \emph{nodataLabel} value are ignored by the fusion process. In case of not unique class labels
// with the maximal belief function, the output pixels are set to the \emph{undecidedLabel} value.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
// We start by including the appropriate header files.


#include "otbImageListToVectorImageFilter.h"
#include "otbConfusionMatrixToMassOfBelief.h"
#include "otbDSFusionOfClassifiersImageFilter.h"

#include <fstream>

#include "otbImageFileReader.h"
#include "otbImageFileWriter.h"


// We will assume unsigned short type input labeled images. We define a type for
// confusion matrices as \doxygen{itk}{VariableSizeMatrix} which will be used to estimate the masses of belief of all the
// class labels for each input classification map. For this purpose, the
// \doxygen{otb}{ConfusionMatrixToMassOfBelief} will be used to convert each input confusion matrix
// into masses of belief for each class label.


50 51 52 53 54
using LabelPixelType                    = unsigned short;
using ConfusionMatrixEltType            = unsigned long;
using ConfusionMatrixType               = itk::VariableSizeMatrix<ConfusionMatrixEltType>;
using ConfusionMatrixToMassOfBeliefType = otb::ConfusionMatrixToMassOfBelief<ConfusionMatrixType, LabelPixelType>;
using MapOfClassesType                  = ConfusionMatrixToMassOfBeliefType::MapOfClassesType;
55

56

57 58 59 60
int CSVConfusionMatrixFileReader(const std::string fileName, MapOfClassesType& mapOfClassesRefClX, ConfusionMatrixType& confusionMatrixClX)
{
  std::ifstream inFile;
  inFile.open(fileName);
61

62 63 64 65 66 67 68
  if (!inFile)
  {
    std::cerr << "Confusion Matrix File opening problem with file:" << std::endl;
    std::cerr << fileName << std::endl;
    return EXIT_FAILURE;
  }
  else
69
  {
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    LabelPixelType labelRef = 0, labelProd = 0;
    std::string    currentLine, refLabelsLine, prodLabelsLine, currentValue;
    const char     endCommentChar = ':';
    const char     separatorChar  = ',';
    const char     eolChar        = '\n';
    std::getline(inFile, refLabelsLine, endCommentChar);  // Skips the comments
    std::getline(inFile, refLabelsLine, eolChar);         // Gets the first line after the comment char until the End Of Line char
    std::getline(inFile, prodLabelsLine, endCommentChar); // Skips the comments
    std::getline(inFile, prodLabelsLine, eolChar);        // Gets the second line after the comment char until the End Of Line char

    std::istringstream issRefLabelsLine(refLabelsLine);
    std::istringstream issProdLabelsLine(prodLabelsLine);

    MapOfClassesType mapOfClassesProdClX;

    mapOfClassesRefClX.clear();
    mapOfClassesProdClX.clear();
    int itLab = 0;
    while (issRefLabelsLine.good())
    {
      std::getline(issRefLabelsLine, currentValue, separatorChar);
      labelRef                     = static_cast<LabelPixelType>(std::atoi(currentValue.c_str()));
      mapOfClassesRefClX[labelRef] = itLab;
      ++itLab;
    }

    itLab = 0;
    while (issProdLabelsLine.good())
    {
      std::getline(issProdLabelsLine, currentValue, separatorChar);
      labelProd                      = static_cast<LabelPixelType>(std::atoi(currentValue.c_str()));
      mapOfClassesProdClX[labelProd] = itLab;
      ++itLab;
    }
104

105 106 107 108 109 110 111 112 113 114 115 116 117 118
    unsigned int        nbRefLabelsClk  = mapOfClassesRefClX.size();
    unsigned int        nbProdLabelsClk = mapOfClassesProdClX.size();
    ConfusionMatrixType confusionMatrixClXTemp;
    confusionMatrixClXTemp = ConfusionMatrixType(nbRefLabelsClk, nbProdLabelsClk);
    confusionMatrixClXTemp.Fill(0);

    // Reading the confusion matrix confusionMatrixClXTemp from the file
    for (unsigned int itRow = 0; itRow < nbRefLabelsClk; ++itRow)
    {
      // Gets the itRow^th line after the header lines with the labels
      std::getline(inFile, currentLine, eolChar);
      std::istringstream issCurrentLine(currentLine);
      unsigned int       itCol = 0;
      while (issCurrentLine.good())
119
      {
120 121 122
        std::getline(issCurrentLine, currentValue, separatorChar);
        confusionMatrixClXTemp(itRow, itCol) = static_cast<ConfusionMatrixEltType>(std::atoi(currentValue.c_str()));
        ++itCol;
123
      }
124
    }
125

126
    MapOfClassesType::iterator itMapOfClassesRef, itMapOfClassesProd;
127

128 129 130 131 132 133 134 135 136 137 138 139
    // Formatting confusionMatrixClX from confusionMatrixClXTemp in order to make confusionMatrixClX a square matrix
    // from the reference labels in mapOfClassesRefClX
    int indiceLabelRef = 0, indiceLabelProd = 0;
    int indiceLabelRefTemp = 0, indiceLabelProdTemp = 0;
    // Initialization of confusionMatrixClX
    confusionMatrixClX = ConfusionMatrixType(nbRefLabelsClk, nbRefLabelsClk);
    confusionMatrixClX.Fill(0);
    for (itMapOfClassesRef = mapOfClassesRefClX.begin(); itMapOfClassesRef != mapOfClassesRefClX.end(); ++itMapOfClassesRef)
    {
      // labels labelRef of mapOfClassesRefClX are already sorted
      labelRef           = itMapOfClassesRef->first;
      indiceLabelRefTemp = itMapOfClassesRef->second;
140

141 142 143 144 145
      for (itMapOfClassesProd = mapOfClassesProdClX.begin(); itMapOfClassesProd != mapOfClassesProdClX.end(); ++itMapOfClassesProd)
      {
        // labels labelProd of mapOfClassesProdClX are already sorted
        labelProd           = itMapOfClassesProd->first;
        indiceLabelProdTemp = itMapOfClassesProd->second;
146

147 148
        // If labelProd is present in mapOfClassesRefClX
        if (mapOfClassesRefClX.count(labelProd) != 0)
149
        {
150
          // Indice of labelProd in mapOfClassesRefClX; itMapOfClassesRef->second elements are already SORTED
151
          indiceLabelProd = mapOfClassesRefClX[labelProd];
152
          confusionMatrixClX(indiceLabelRef, indiceLabelProd) = confusionMatrixClXTemp(indiceLabelRefTemp, indiceLabelProdTemp);
153 154
        }
      }
155 156
      ++indiceLabelRef;
    }
157
  }
158 159 160
  inFile.close();
  return EXIT_SUCCESS;
}
161 162


163
int main(int argc, char* argv[])
164
{
165
  // The input labeled images to be fused are expected to be scalar images.
166 167 168
  const unsigned int Dimension = 2;
  using LabelImageType         = otb::Image<LabelPixelType, Dimension>;
  using VectorImageType        = otb::VectorImage<LabelPixelType, Dimension>;
169

170
  LabelPixelType nodataLabel    = atoi(argv[argc - 3]);
171
  LabelPixelType undecidedLabel = atoi(argv[argc - 2]);
172
  const char*    outfname       = argv[argc - 1];
173

174
  unsigned int nbParameters         = 3;
175 176 177 178 179 180
  unsigned int nbClassificationMaps = (argc - 1 - nbParameters) / 2;

  // We declare an \doxygen{otb}{ImageListToVectorImageFilter} which will stack all the
  // input classification maps to be fused as a single VectorImage for which each
  // band is a classification map. This VectorImage will then be the input of the
  // Dempster Shafer fusion filter \doxygen{otb}{DSFusionOfClassifiersImageFilter}.
181 182
  using LabelImageListType               = otb::ImageList<LabelImageType>;
  using ImageListToVectorImageFilterType = otb::ImageListToVectorImageFilter<LabelImageListType, VectorImageType>;
183

184
  using MassOfBeliefDefinitionMethod = ConfusionMatrixToMassOfBeliefType::MassOfBeliefDefinitionMethod;
185 186


187
  // The Dempster Shafer fusion filter \doxygen{otb}{DSFusionOfClassifiersImageFilter} is declared.
188
  // Dempster Shafer
189
  using DSFusionOfClassifiersImageFilterType = otb::DSFusionOfClassifiersImageFilter<VectorImageType, LabelImageType>;
190

191
  using VectorOfMapOfMassesOfBeliefType = DSFusionOfClassifiersImageFilterType::VectorOfMapOfMassesOfBeliefType;
192

193 194 195
  // Both reader and writer are defined. Since the images
  // to classify can be very big, we will use a streamed writer which
  // will trigger the streaming ability of the fusion filter.
196 197
  using ReaderType = otb::ImageFileReader<LabelImageType>;
  using WriterType = otb::ImageFileWriter<LabelImageType>;
198 199 200 201 202 203


  // The image list of input classification maps is filled. Moreover, the input
  // confusion matrix files are converted into masses of belief.
  ReaderType::Pointer                        reader;
  LabelImageListType::Pointer                imageList = LabelImageListType::New();
204 205 206 207 208 209 210 211 212 213 214
  ConfusionMatrixToMassOfBeliefType::Pointer confusionMatrixToMassOfBeliefFilter;
  confusionMatrixToMassOfBeliefFilter = ConfusionMatrixToMassOfBeliefType::New();

  MassOfBeliefDefinitionMethod massOfBeliefDef;

  // Several parameters are available to estimate the masses of belief
  // from the confusion matrices: PRECISION, RECALL, ACCURACY and KAPPA
  massOfBeliefDef = ConfusionMatrixToMassOfBeliefType::PRECISION;

  VectorOfMapOfMassesOfBeliefType vectorOfMapOfMassesOfBelief;
  for (unsigned int itCM = 0; itCM < nbClassificationMaps; ++itCM)
215
  {
216
    std::string fileNameClassifiedImage = argv[itCM + 1];
217
    std::string fileNameConfMat         = argv[itCM + 1 + nbClassificationMaps];
218 219 220 221 222 223 224

    reader = ReaderType::New();
    reader->SetFileName(fileNameClassifiedImage);
    reader->Update();

    imageList->PushBack(reader->GetOutput());

225
    MapOfClassesType    mapOfClassesClk;
226 227 228 229
    ConfusionMatrixType confusionMatrixClk;

    // The data (class labels and confusion matrix values) are read and
    // extracted from the *.CSV file with an ad-hoc file parser
230
    CSVConfusionMatrixFileReader(fileNameConfMat, mapOfClassesClk, confusionMatrixClk);
231 232 233 234 235 236 237

    // The parameters of the ConfusionMatrixToMassOfBelief filter are set
    confusionMatrixToMassOfBeliefFilter->SetMapOfClasses(mapOfClassesClk);
    confusionMatrixToMassOfBeliefFilter->SetConfusionMatrix(confusionMatrixClk);
    confusionMatrixToMassOfBeliefFilter->SetDefinitionMethod(massOfBeliefDef);
    confusionMatrixToMassOfBeliefFilter->Update();

238 239
    // Vector containing ALL the K (= nbClassificationMaps) std::map<Label, MOB>
    // of Masses of Belief
240 241
    vectorOfMapOfMassesOfBelief.push_back(confusionMatrixToMassOfBeliefFilter->GetMapMassOfBelief());
  }
242 243


244 245
  // The image list of input classification maps is converted into a VectorImage to
  // be used as input of the \doxygen{otb}{DSFusionOfClassifiersImageFilter}.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
  // Image List To VectorImage
  ImageListToVectorImageFilterType::Pointer imageListToVectorImageFilter;
  imageListToVectorImageFilter = ImageListToVectorImageFilterType::New();
  imageListToVectorImageFilter->SetInput(imageList);

  DSFusionOfClassifiersImageFilterType::Pointer dsFusionFilter;
  dsFusionFilter = DSFusionOfClassifiersImageFilterType::New();

  // The parameters of the DSFusionOfClassifiersImageFilter are set
  dsFusionFilter->SetInput(imageListToVectorImageFilter->GetOutput());
  dsFusionFilter->SetInputMapsOfMassesOfBelief(&vectorOfMapOfMassesOfBelief);
  dsFusionFilter->SetLabelForNoDataPixels(nodataLabel);
  dsFusionFilter->SetLabelForUndecidedPixels(undecidedLabel);


261 262
  // Once it is plugged the pipeline triggers its execution by updating
  // the output of the writer.
263 264 265 266 267 268 269

  WriterType::Pointer writer = WriterType::New();
  writer->SetInput(dsFusionFilter->GetOutput());
  writer->SetFileName(outfname);
  writer->Update();
  return EXIT_SUCCESS;
}