Medical Imaging Interaction Toolkit  2018.4.99-389bf124
Medical Imaging Interaction Toolkit
mitkCollectionStatistic.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
14 #include <mitkDataCollection.h>
15 
16 // DataCollection Stuff
18 //stl stuff
19 #include <sstream>
20 
21 #include <itkShapedNeighborhoodIterator.h>
22 #include <itkSignedDanielssonDistanceMapImageFilter.h>
23 
24 
26  : m_GroundTruthValueToIndexMapper(nullptr)
27  , m_TestValueToIndexMapper(nullptr)
28 {
29 
30 }
31 
33 {
34  m_GroundTruthValueToIndexMapper = nullptr;
35  m_TestValueToIndexMapper = nullptr;
36 }
37 
38 
40 {
41  m_Collection = collection;
42 }
43 
45 {
46  return m_Collection;
47 }
48 
50 {
51  m_ClassCount = count;
52 }
53 
55 {
56  return m_ClassCount;
57 }
58 
60 {
61  m_GroundTruthName = name;
62 }
63 
65 {
66  return m_GroundTruthName;
67 }
68 
70 {
71  m_TestName = name;
72 }
73 
75 {
76  return m_TestName;
77 }
78 
80 {
81  m_GroundTruthValueToIndexMapper = mapper;
82 }
83 
85 {
86  return m_GroundTruthValueToIndexMapper;
87 }
88 
90 {
91  m_TestValueToIndexMapper = mapper;
92 }
93 
95 {
96  return m_TestValueToIndexMapper;
97 }
98 
99 int mitk::CollectionStatistic::IsInSameVirtualClass(unsigned char gold, unsigned char test)
100 {
101  int resultClass = -1;
102  for (std::size_t i = 0; i < m_ConnectionGold.size(); ++i)
103  {
104  if (m_ConnectionGold[i] == gold && m_ConnectionTest[i] == test)
105  {
106  resultClass = m_ConnectionClass[i];
107  break;
108  }
109  }
110  return resultClass;
111 }
112 
114 {
115  if (m_GroundTruthValueToIndexMapper == nullptr)
116  {
117  MITK_ERROR << "m_GroundTruthValueToIndexMapper is nullptr";
118  return false;
119  }
120 
121  if (m_TestValueToIndexMapper == nullptr)
122  {
123  MITK_ERROR << "m_TestValueToIndexMapper is nullptr";
124  return false;
125  }
126 
127  DataCollectionImageIterator<unsigned char, 3> goldIter(m_Collection, m_GroundTruthName);
128  DataCollectionImageIterator<unsigned char, 3> testIter(m_Collection, m_TestName);
129  DataCollectionImageIterator<unsigned char, 3> maskIter(m_Collection, m_MaskName);
130 
131  int index = 0;
132 
133  while (!goldIter.IsAtEnd())
134  {
135  std::size_t imageIndex = goldIter.GetImageIndex();
136  if (m_ImageClassStatistic.size() <= imageIndex)
137  {
138  MITK_INFO << "New Image: " << goldIter.GetFilePrefix();
139  m_ImageNames.push_back(goldIter.GetFilePrefix());
140  StatisticData statData;
141  m_ImageStatistic.push_back(statData);
142  DataVector data;
143  for (std::size_t i = 0; i < m_ClassCount; ++i)
144  {
145  StatisticData stat;
146  data.push_back(stat);
147  }
148  m_ImageClassStatistic.push_back(data);
149  }
150 
151  if (maskIter.GetVoxel() <= 0)
152  {
153  ++goldIter;
154  ++testIter;
155  ++maskIter;
156  continue;
157  }
158 
159  ++index;
160  unsigned char goldClass = m_GroundTruthValueToIndexMapper->operator()(goldIter.GetVoxel());
161  unsigned char testClass = m_TestValueToIndexMapper->operator()(testIter.GetVoxel());
162  if (goldClass == testClass) // True Positive
163  {
164  m_ImageStatistic[imageIndex].m_TruePositive += 1;
165  for (std::size_t i = 0; i < m_ClassCount; ++i)
166  {
167  if (goldClass == i) // For the detected class it is a true positive
168  {
169  m_ImageClassStatistic[imageIndex][i].m_TruePositive += 1;
170  } else // for all other classes than the detected it is a true negative
171  {
172  m_ImageClassStatistic[imageIndex][i].m_TrueNegative += 1;
173  }
174  }
175  } else // No True Positive
176  {
177  m_ImageStatistic[imageIndex].m_FalseNegative += 1;
178  m_ImageStatistic[imageIndex].m_FalsePositive += 1;
179  for (std::size_t i = 0; i < m_ClassCount; ++i)
180  {
181  if (goldClass == i) // For the class in Goldstandard it is a false negative
182  {
183  m_ImageClassStatistic[imageIndex][i].m_FalseNegative += 1;
184  } else if ( testClass == i) // For the test class it is a false positive
185  {
186  m_ImageClassStatistic[imageIndex][i].m_FalsePositive += 1;
187  } else // For all other it is a true negative
188  {
189  m_ImageClassStatistic[imageIndex][i].m_TrueNegative += 1;
190  }
191  }
192  }
193 
194  ++goldIter;
195  ++testIter;
196  ++maskIter;
197  }
198  MITK_INFO << "Evaluated " << index << " points";
199  return true;
200 }
201 
202 void mitk::CollectionStatistic::Print(std::ostream& out, std::ostream& sout, bool withHeader, std::string label)
203 {
204  assert(m_ImageClassStatistic.size() > 0);
205  assert(m_ImageClassStatistic[0].size() == m_ClassCount);
206 
207  if (withHeader)
208  {
209  sout << "Label;ImageName;";
210  for (std::size_t i = 0; i < m_ClassCount; ++i)
211  {
212  sout << "DICE-Class-"<< i << ";";
213  sout << "Jaccard-Class-"<< i << ";";
214  sout << "Sensitivity-Class-"<< i << ";";
215  sout << "Specificity-Class-"<< i << ";";
216  sout << "TP-Class-"<< i << ";";
217  sout << "TN-Class-"<< i << ";";
218  sout << "FP-Class-"<< i << ";";
219  sout << "FN-Class-"<< i << ";";
220  }
221  sout << "DICE-MEAN"<< ";";
222  sout << "Jaccard-MEAN"<< ";";
223  sout << "Sensitivity-MEAN"<< ";";
224  sout << "Specificity-MEAN"<< ";";
225  sout << "TP-MEAN"<< ";";
226  sout << "TN-MEAN"<< ";";
227  sout << "FP-MEAN"<< ";";
228  sout << "FN-MEAN"<< ";";
229  sout << "DICE-WMEAN"<< ";";
230  sout << "Jaccard-WMEAN"<< ";";
231  sout << "Sensitivity-WMEAN"<< ";";
232  sout << "Specificity-WMEAN"<< ";";
233  sout << "TP-WMEAN"<< ";";
234  sout << "TN-WMEAN"<< ";";
235  sout << "FP-WMEAN"<< ";";
236  sout << "FN-WMEAN"<< ";";
237  sout << "COMPLETE-TRUE/FALSE"<< ";";
238  sout << "COMPLETE-TRUES"<< ";";
239  sout << "COMPLETE_FALSE"<< ";";
240  sout << std::endl;
241  }
242  out << std::setprecision(5);
243 
244 
245  MITK_INFO << "m_ImageClassStatistic.size(): " << m_ImageClassStatistic.size();
246 
247  for (std::size_t i = 0; i < m_ImageClassStatistic.size(); ++i)
248  {
249  sout << label << ";"<< m_ImageNames[i]<<";";
250  StatisticData meanStat;
251  StatisticData wMeanStat;
252  double pointsSum = 0;
253 
254  out << "======================================================== Image " << std::setw(3) << i << " ========================================================" << std::endl;
255  out << " Image ID : " << m_ImageNames[i] <<std::endl;
256 
257  out << "|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|--------------|" << std::endl;
258  out << "| Class | DICE | Jaccard | Sensitivity | Specificity | TP | TN | FP | FN |" << std::endl;
259  out << "|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|--------------|" << std::endl;
260 
261  for (std::size_t j =0; j < m_ImageClassStatistic[i].size(); ++j)
262  {
263  StatisticData& stat = m_ImageClassStatistic[i][j];
264  stat.m_DICE = std::max(0.0,(2.0 * stat.m_TruePositive) / (2.0 * stat.m_TruePositive + stat.m_FalseNegative + stat.m_FalsePositive));
265  stat.m_Jaccard = std::max(0.0,(1.0 * stat.m_TruePositive) / (1.0 * stat.m_TruePositive + stat.m_FalseNegative + stat.m_FalsePositive));
266  stat.m_Sensitivity = std::max(0.0,(1.0 * stat.m_TruePositive) / (stat.m_TruePositive + stat.m_FalseNegative));
267  stat.m_Specificity = std::max(0.0,(1.0 * stat.m_TrueNegative) / ( stat.m_FalsePositive + stat.m_TrueNegative));
268 
269  meanStat.m_DICE += std::max(stat.m_DICE,0.0);
270  meanStat.m_Jaccard += std::max(stat.m_Jaccard,0.0);
271  meanStat.m_Sensitivity += std::max(stat.m_Sensitivity,0.0);
272  meanStat.m_Specificity += std::max(stat.m_Specificity,0.0);
273  meanStat.m_TruePositive += stat.m_TruePositive;
274  meanStat.m_TrueNegative += stat.m_TrueNegative;
275  meanStat.m_FalsePositive += stat.m_FalsePositive;
276  meanStat.m_FalseNegative += stat.m_FalseNegative;
277 
278  double points = stat.m_TruePositive + stat.m_FalseNegative;
279  pointsSum += points;
280 
281  wMeanStat.m_DICE += std::max(stat.m_DICE,0.0) * points;
282  wMeanStat.m_Jaccard += std::max(stat.m_Jaccard,0.0) * points;
283  wMeanStat.m_Sensitivity += std::max(stat.m_Sensitivity,0.0) * points;
284  wMeanStat.m_Specificity += std::max(stat.m_Specificity,0.0) * points;
285  wMeanStat.m_TruePositive += stat.m_TruePositive;
286  wMeanStat.m_TrueNegative += stat.m_TrueNegative;
287  wMeanStat.m_FalsePositive += stat.m_FalsePositive;
288  wMeanStat.m_FalseNegative += stat.m_FalseNegative;
289 
290  out<< "|" << std::setw(7) << j << " | ";
291  out << std::setw(11) << stat.m_DICE << " | ";
292  out << std::setw(11) << stat.m_Jaccard << " | ";
293  out << std::setw(11) << stat.m_Sensitivity << " | ";
294  out << std::setw(11) << stat.m_Specificity << " | ";
295  out << std::setw(11) << stat.m_TruePositive << " | ";
296  out << std::setw(11) << stat.m_TrueNegative<< " | ";
297  out << std::setw(11) << stat.m_FalsePositive<< " | ";
298  out << std::setw(11) << stat.m_FalseNegative << " |"<< std::endl;
299  sout << stat.m_DICE << ";";
300  sout << stat.m_Jaccard << ";";
301  sout << stat.m_Sensitivity << ";";
302  sout << stat.m_Specificity << ";";
303  sout << stat.m_TruePositive << ";";
304  sout << stat.m_TrueNegative<< ";";
305  sout << stat.m_FalsePositive<< ";";
306  sout << stat.m_FalseNegative << ";";
307  sout << stat.m_RMSD << ";";
308  }
309 
310  meanStat.m_DICE /= m_ImageClassStatistic[i].size();
311  meanStat.m_Jaccard /= m_ImageClassStatistic[i].size();
312  meanStat.m_Sensitivity /= m_ImageClassStatistic[i].size();
313  meanStat.m_Specificity /= m_ImageClassStatistic[i].size();
314  meanStat.m_TruePositive /= m_ImageClassStatistic[i].size();
315  meanStat.m_TrueNegative /= m_ImageClassStatistic[i].size();
316  meanStat.m_FalsePositive /= m_ImageClassStatistic[i].size();
317  meanStat.m_FalseNegative /= m_ImageClassStatistic[i].size();
318 
319  out << "|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|--------------|" << std::endl;
320  out << std::setw(7) << "| Mean "<< " | ";
321  out << std::setw(11) << meanStat.m_DICE << " | ";
322  out << std::setw(11) << meanStat.m_Jaccard << " | ";
323  out << std::setw(11) << meanStat.m_Sensitivity << " | ";
324  out << std::setw(11) << meanStat.m_Specificity << " | ";
325  out << std::setw(11) << meanStat.m_TruePositive << " | ";
326  out << std::setw(11) << meanStat.m_TrueNegative<< " | ";
327  out << std::setw(11) << meanStat.m_FalsePositive<< " | ";
328  out << std::setw(11) << meanStat.m_FalseNegative << " |"<< std::endl;
329  sout << meanStat.m_DICE << ";";
330  sout << meanStat.m_Jaccard << ";";
331  sout << meanStat.m_Sensitivity << ";";
332  sout << meanStat.m_Specificity << ";";
333  sout << meanStat.m_TruePositive << ";";
334  sout << meanStat.m_TrueNegative<< ";";
335  sout << meanStat.m_FalsePositive<< ";";
336  sout << meanStat.m_FalseNegative << ";";
337 
338  wMeanStat.m_DICE /= pointsSum;
339  wMeanStat.m_Jaccard /= pointsSum;
340  wMeanStat.m_Sensitivity /= pointsSum;
341  wMeanStat.m_Specificity /= pointsSum;
342  wMeanStat.m_TruePositive /= pointsSum;
343  wMeanStat.m_TrueNegative /= pointsSum;
344  wMeanStat.m_FalsePositive /= pointsSum;
345  wMeanStat.m_FalseNegative /= pointsSum;
346 
347  out << std::setw(7) << "| W-Mean"<< " | ";
348  out << std::setw(11) << wMeanStat.m_DICE << " | ";
349  out << std::setw(11) << wMeanStat.m_Jaccard << " | ";
350  out << std::setw(11) << wMeanStat.m_Sensitivity << " | ";
351  out << std::setw(11) << wMeanStat.m_Specificity << " | ";
352  out << std::setw(11) << wMeanStat.m_TruePositive << " | ";
353  out << std::setw(11) << wMeanStat.m_TrueNegative<< " | ";
354  out << std::setw(11) << wMeanStat.m_FalsePositive<< " | ";
355  out << std::setw(11) << wMeanStat.m_FalseNegative << " |"<< std::endl;
356  sout << wMeanStat.m_DICE << ";";
357  sout << wMeanStat.m_Jaccard << ";";
358  sout << wMeanStat.m_Sensitivity << ";";
359  sout << wMeanStat.m_Specificity << ";";
360  sout << wMeanStat.m_TruePositive << ";";
361  sout << wMeanStat.m_TrueNegative<< ";";
362  sout << wMeanStat.m_FalsePositive<< ";";
363  sout << wMeanStat.m_FalseNegative << ";";
364 
365  m_ImageStatistic[i].m_Sensitivity = std::max(0.0,(1.0 * m_ImageStatistic[i].m_TruePositive) / (m_ImageStatistic[i].m_TruePositive + m_ImageStatistic[i].m_FalseNegative));
366  m_ImageStatistic[i].m_Specificity = std::max(0.0,(1.0 * m_ImageStatistic[i].m_TrueNegative) / ( m_ImageStatistic[i].m_FalsePositive + m_ImageStatistic[i].m_TrueNegative));
367 
368  out << std::setw(7) << "| Compl."<< " | ";
369  out << std::setw(11) << " x " << " | ";
370  out << std::setw(11) << " x " << " | ";
371  out << std::setw(11) << m_ImageStatistic[i].m_Sensitivity << " | ";
372  out << std::setw(11) << " x " << " | ";
373  out << std::setw(11) << m_ImageStatistic[i].m_TruePositive << " | ";
374  out << std::setw(11) << m_ImageStatistic[i].m_TrueNegative<< " | ";
375  out << std::setw(11) << m_ImageStatistic[i].m_FalsePositive<< " | ";
376  out << std::setw(11) << m_ImageStatistic[i].m_FalseNegative << " |"<< std::endl;
377  out << "|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|--------------|" << std::endl;
378 
379  sout << m_ImageStatistic[i].m_Sensitivity << ";";
380  sout << m_ImageStatistic[i].m_TruePositive << ";";
381  sout << m_ImageStatistic[i].m_FalsePositive<< std::endl;
382  out << std::endl;
383  }
384 }
385 
386 std::vector<mitk::StatisticData> mitk::CollectionStatistic::GetStatisticData(unsigned char c) const
387 {
388  std::vector<StatisticData> statistics;
389 
390  for (size_t i = 0; i < m_ImageClassStatistic.size(); i++)
391  {
392  statistics.push_back(m_ImageClassStatistic[i][c]);
393  }
394 
395  return statistics;
396 }
397 
399 {
400  assert(m_ClassCount == 2);
401  assert(m_GroundTruthValueToIndexMapper != nullptr);
402  assert(m_TestValueToIndexMapper != nullptr);
403 
404  DataCollectionImageIterator<unsigned char, 3> groundTruthIter(m_Collection, m_GroundTruthName);
405  DataCollectionImageIterator<unsigned char, 3> testIter(m_Collection, m_TestName);
406  DataCollectionImageIterator<unsigned char, 3> maskIter(m_Collection, m_MaskName);
407 
408  typedef itk::Image<unsigned char, 3> LabelImage;
409  typedef itk::Image<double, 3> ImageType;
410  typedef itk::SignedDanielssonDistanceMapImageFilter<LabelImage, ImageType, ImageType> DistanceMapFilterType;
411  typedef itk::ConstantBoundaryCondition<LabelImage> BoundaryConditionType;
412  typedef itk::ConstShapedNeighborhoodIterator<LabelImage, BoundaryConditionType> ConstNeighborhoodIteratorType;
413 
414  // Build up 6-neighborhood. Diagonal voxel are maybe not considered for distance map computation.
415  // So 6-neighborhood avoids inconsistencies.
416  ConstNeighborhoodIteratorType::OffsetType offset0 = {{ 0, 0, -1}};
417  ConstNeighborhoodIteratorType::OffsetType offset1 = {{ 0, 0, 1}};
418  ConstNeighborhoodIteratorType::OffsetType offset2 = {{ 0, -1, 0}};
419  ConstNeighborhoodIteratorType::OffsetType offset3 = {{ 0, 1, 0}};
420  ConstNeighborhoodIteratorType::OffsetType offset4 = {{-1, 0, 0}};
421  ConstNeighborhoodIteratorType::OffsetType offset5 = {{ 1, 0, 0}};
422 
423  const int outsideVal = 17;
424  itk::NeighborhoodIterator<LabelImage>::RadiusType radius;
425  radius.Fill(1);
426 
427  BoundaryConditionType bc;
428  // if a neighborhood voxel is outside of the image region a default value is returned
429  bc.SetConstant(outsideVal);
430 
431  ConstNeighborhoodIteratorType neighborhoodIter;
432 
433  ImageType::Pointer distanceImage;
434  std::vector<mitk::StatisticData>* currentImageStatistics = nullptr;
435 
436  unsigned int distanceBorderSamples = 0;
437  double totalBorderRMSDistance = 0;
438 
439  int previousImageIndex = -1;
440 
441  while (!testIter.IsAtEnd())
442  {
443  int currentImageIndex = testIter.GetImageIndex();
444 
445  // prepare data for next image
446  if (previousImageIndex != currentImageIndex)
447  {
448  previousImageIndex = currentImageIndex;
449 
450  currentImageStatistics = &(m_ImageClassStatistic.at(currentImageIndex));
451 
452  distanceBorderSamples = 0;
453  totalBorderRMSDistance = 0;
454 
455  // generate distance map from gold standard image
456  DistanceMapFilterType::Pointer distanceMapFilter = DistanceMapFilterType::New();
457  distanceMapFilter->SetInput(groundTruthIter.GetImageIterator().GetImage());
458  distanceMapFilter->SetUseImageSpacing(true);
459  distanceMapFilter->Update();
460  distanceImage = distanceMapFilter->GetOutput();
461 
462  neighborhoodIter = ConstNeighborhoodIteratorType(radius, testIter.GetImageIterator().GetImage(), testIter.GetImageIterator().GetImage()->GetRequestedRegion());
463  neighborhoodIter.OverrideBoundaryCondition(&bc);
464 
465  // configure 6-neighborhood
466  neighborhoodIter.ActivateOffset(offset0);
467  neighborhoodIter.ActivateOffset(offset1);
468  neighborhoodIter.ActivateOffset(offset2);
469  neighborhoodIter.ActivateOffset(offset3);
470  neighborhoodIter.ActivateOffset(offset4);
471  neighborhoodIter.ActivateOffset(offset5);
472  }
473 
474  unsigned char testClass = m_TestValueToIndexMapper->operator()(testIter.GetVoxel());
475 
476  if ( maskIter.GetVoxel() > 0 && testClass != 0)
477  {
478  // check if it is a border voxel
479  neighborhoodIter.SetLocation(testIter.GetImageIterator().GetIndex());
480  bool border = false;
481 
482  ConstNeighborhoodIteratorType::ConstIterator iter;
483  for (iter = neighborhoodIter.Begin(); !iter.IsAtEnd(); iter++)
484  {
485  if (iter.Get() == outsideVal)
486  {
487  continue;
488  }
489 
490  if (m_TestValueToIndexMapper->operator()(iter.Get()) != 1 )
491  {
492  border = true;
493  break;
494  }
495  }
496 
497  if (border)
498  {
499  double currentDistance = distanceImage->GetPixel(testIter.GetImageIterator().GetIndex());
500  totalBorderRMSDistance += currentDistance * currentDistance;
501  ++distanceBorderSamples;
502 
503  // update immediately, so the final value is set after the iterator of the last image has reached the end
504  double rmsd = std::sqrt(totalBorderRMSDistance / (double) distanceBorderSamples);
505  currentImageStatistics->at(1).m_RMSD = rmsd;
506  }
507  }
508  ++groundTruthIter;
509  ++testIter;
510  ++maskIter;
511  }
512 }
const ValueToIndexMapper * GetTestValueToIndexMapper(void) const
#define MITK_INFO
Definition: mitkLogMacros.h:18
void Print(std::ostream &out, std::ostream &sout=std::cout, bool withHeader=false, std::string label="None")
void SetTestName(std::string name)
itk::Image< unsigned char, 3 > ImageType
#define MITK_ERROR
Definition: mitkLogMacros.h:20
Follow Up Storage - Class to facilitate loading/accessing structured follow-up data.
Definition: testcase.h:28
void SetTestValueToIndexMapper(const ValueToIndexMapper *mapper)
void SetGroundTruthValueToIndexMapper(const ValueToIndexMapper *mapper)
DataCollection::Pointer GetCollection()
void SetGoldName(std::string name)
const ValueToIndexMapper * GetGroundTruthValueToIndexMapper(void) const
void ComputeRMSD()
Computes root-mean-square distance of two binary images.
void SetCollection(DataCollection::Pointer collection)
void SetClassCount(vcl_size_t count)
static T max(T x, T y)
Definition: svm.cpp:56
std::vector< StatisticData > GetStatisticData(unsigned char c) const
mitk::CollectionStatistic::GetStatisticData
std::vector< StatisticData > DataVector
itk::SmartPointer< Self > Pointer
Definition: mitkBaseData.h:41
int IsInSameVirtualClass(unsigned char gold, unsigned char test)