DGtalTools  0.9.4
volShapeMetrics.cpp
1 
29 #include <iostream>
30 #include <DGtal/base/Common.h>
31 #include <DGtal/io/readers/GenericReader.h>
32 #include <DGtal/io/writers/GenericWriter.h>
33 #include <DGtal/helpers/StdDefs.h>
34 #include <DGtal/images/Image.h>
35 #include <DGtal/images/ImageContainerBySTLVector.h>
36 #include <DGtal/images/imagesSetsUtils/SetFromImage.h>
37 #include <DGtal/geometry/volumes/distance/DistanceTransformation.h>
38 #include <DGtal/math/Statistic.h>
39 
40 #include <boost/program_options/options_description.hpp>
41 #include <boost/program_options/parsers.hpp>
42 #include <boost/program_options/variables_map.hpp>
43 #include <limits>
44 
45 
46 using namespace std;
47 using namespace DGtal;
48 using namespace Z3i;
49 
50 namespace po = boost::program_options;
51 
52 
53 
128 
129 
130 bool
131 isDiff(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
132  int bMin, int bMax, const Point &pt){
133  bool isRefOn = (imageA(pt)<= aMax) && (imageA(pt) >= aMin);
134  bool isCompOn = (imageB(pt)<= bMax) && (imageB(pt) >= bMin);
135  return ((!isRefOn && isCompOn) || (isRefOn && !isCompOn));
136 }
137 
138 
139 
140 void
141 getStatsFromDistanceMap(Statistic<double> & stats, const Image3D &imageA, int aMin, int aMax,
142  const Image3D & imageB, int bMin, int bMax,
143  bool statOnFalsePositiveOnly=false, Point *ptMax=0){
144 
145  // Get the digital set from ref image by computing the surface (use -1 and +1 since the interval of append function are open)
146  Z3i::DigitalSet set3dRef (imageA.domain());
147  SetFromImage<Z3i::DigitalSet>::append<Image3D>(set3dRef, imageA, aMin-1,aMax);
149 
150 
151  // Applying the distance transform on the digital surface of the set:
153  const NegPredicate aPredicate( set3dRef );
154  DTL2 dtL2( imageA.domain(), aPredicate, Z3i::l2Metric );
155 
156  // Get the set of point of imageB: (use -1 and +1 since the interval of append function are open)
157  Z3i::DigitalSet set3dComp (imageB.domain());
158  SetFromImage<Z3i::DigitalSet>::append<Image3D>(set3dComp, imageB, bMin-1, bMax);
159 
160  unsigned int nbAdded=0;
161  double maxDist=0;
162  //Applying stats from the set to be compared (from imageB)
163  for(Z3i::DigitalSet::ConstIterator it= set3dComp.begin(); it!= set3dComp.end(); ++it){
164  if((!statOnFalsePositiveOnly) || (isDiff(imageA, aMin, aMax, imageB, bMin, bMax, *it))){
165  DTL2::Value distance = dtL2(*it);
166  stats.addValue(distance);
167  nbAdded++;
168  if(maxDist<distance){
169  maxDist=distance;
170  if(ptMax!=0){
171  (*ptMax)[0]=(*it)[0];
172  (*ptMax)[1]=(*it)[1];
173  (*ptMax)[2]=(*it)[2];
174  }
175  }
176  }
177  }
178 
179  if(nbAdded==0)
180  trace.error() << "No point added to statistics, will failed..." << endl;
181 }
182 
183 
184 
185 void
186 getVoxelsStats(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
187  int bMin, int bMax, bool exportStatVoxels, std::vector<Point> &vectPtBinA,
188  std::vector<Point> &vectPtCompBinCompA, std::vector<Point> &vectPtBnotInA,
189  std::vector<Point> &vectPtnotBInA, bool precisionRecallFMean ){
190  int numBinA = 0; // true positif with A as ref shape.
191  int numCompBinCompA = 0; // true neg with A as ref shape.
192  int numBnotInA = 0; // false pos with A as ref shape
193  int numNotBinA = 0; // false neg with A as ref shape
194  int numTotalInA = 0; // total pos in reference shape
195  int numTotalInB = 0; // total pos in compared shape
196  int numTotalinCompA = 0; //total in complement A
197  int numTotalinCompB = 0; //total in complement B
198 
199  for(Image3D::Domain::ConstIterator it = imageA.domain().begin(); it!=imageA.domain().end(); it++){
200  // voxels in A
201  if(imageA(*it) <= aMax && imageA(*it) >= aMin){
202  numTotalInA++;
203  //voxels in B
204  if(imageB(*it) <= bMax && imageB(*it) >= bMin){
205  numTotalInB++;
206  numBinA++;
207  if(exportStatVoxels) vectPtBinA.push_back(*it);
208  }else{
209  numTotalinCompB++;
210  numNotBinA++;
211  if(exportStatVoxels) vectPtnotBInA.push_back(*it);
212  }
213  // voxels outside A
214  }else{
215  numTotalinCompA++;
216  // voxels in B
217  if(imageB(*it) <= bMax && imageB(*it) >= bMin){
218  numBnotInA++;
219  numTotalInB++;
220  if(exportStatVoxels) vectPtBnotInA.push_back(*it);
221  }else{
222  numTotalinCompB++;
223  numCompBinCompA++;
224  if(exportStatVoxels) vectPtCompBinCompA.push_back(*it);
225  }
226  }
227  }
228 
229  std::cout << numBinA << " " << numCompBinCompA << " " << numBnotInA
230  << " " << numNotBinA << " " << numTotalInA << " "
231  << numTotalInB << " " << numTotalinCompA << " " << numTotalinCompB;
232  if(precisionRecallFMean){
233  double precision = (double)numBinA/(numBinA + numBnotInA);
234  double recall = (double)numBinA/(numBinA+numNotBinA);
235  double fmean = (2.0*precision*recall)/(precision+recall);
236  std::cout << " " << precision<< " " << recall << " " << fmean ;
237  }
238 }
239 
240 
241 
242 
243 
244 
245 // total ref: True Positive, True Negative, False Positive, False Negative
246 void
247 getVoxelsStats(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
248  int bMin, int bMax, bool precisionRecallFMean ){
249  std::vector<Point> v1, v2, v3, v4;
250  return getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, false, v1, v2, v3, v4, precisionRecallFMean);
251 }
252 
253 
254 
255 
256 
257 void
258 exportSetofPoints(string filename, std::vector<Point> aVectPoint){
259  std::ofstream ofs;
260  ofs.open(filename.c_str(), std::ofstream::out );
261  ofs<< "# Set of 3d points with format: x y z" << std::endl;
262  for (unsigned int i =0; i< aVectPoint.size(); i++){
263  ofs << aVectPoint.at(i)[0] << " " << aVectPoint.at(i)[1] << " "<< aVectPoint.at(i)[2] << std::endl;
264  }
265  ofs.close();
266 }
267 
268 
269 
270 
271 
272 
273 
274 int main(int argc, char**argv)
275 {
276 
277 
278  // parse command line ----------------------------------------------
279  // A = ref
280  // B= comp
281  po::options_description general_opt ( "Allowed options are: " );
282  general_opt.add_options()
283  ( "help,h", "display this message." )
284  ( "volA,a", po::value<std::string>(), "Input filename of volume A (vol format, and other pgm3d can also be used)." )
285  ( "volB,b", po::value<std::string>(), "Input filename of volume B (vol format, and other pgm3d can also be used)." )
286  ( "aMin", po::value<int>()->default_value(0), "min threshold for a voxel to be considered as belonging to the object of volume A. (default 0)" )
287  ( "aMax", po::value<int>()->default_value(128), "max threshold for a voxel to be considered as belonging to the object of volume A. (default 128)" )
288  ( "bMin", po::value<int>()->default_value(0), "min threshold for a voxel to be considered as belonging to the object of volume B. (default 0)" )
289  ( "bMax", po::value<int>()->default_value(128), "max threshold for a voxel to be considered as belonging to the object of volume B. (default 128)" )
290  ( "noDistanceComparisons", "to avoid to apply distance map computation if the distance comparaison are not needed.")
291  ("distancesFromBnotInAOnly", "apply distance map measures only for voxels of B which are not in A (else the measure are given from all distances of the object B).")
292  ("displayTFstats", "Change the comparison diplay by using the true/false/positive/negative notation and considering the shape A as reference. It also display precision/recall/f-mean statistics.")
293  ("exportSDP", "Export voxels belonging to each categorie (voxels of ( B in A) , (NOT in B and NOT in A), (B and NOT in A) and (Voxels of NOT in B and in A)). ") ;
294  bool parseOK=true;
295  po::variables_map vm;
296  try{
297  po::store(po::parse_command_line(argc, argv, general_opt), vm);
298  }catch(const std::exception& ex){
299  parseOK=false;
300  trace.info()<< "Error checking program options: "<< ex.what()<< endl;
301  }
302  po::notify(vm);
303 
304  if ( !parseOK || vm.count ( "help" ) || ! vm.count("volA")||! vm.count("volB") )
305  {
306  trace.info() << "Apply shape measures for comparing two volumetric images A and B (shape defined from thresholds). " << std::endl
307  << "It can compute:" << std::endl
308  << " - voxel count from voxel partition (number of voxel from (B-A), (A-B) ...): usefull to determine classical statistics like false positive related stats."<<std::endl
309  << " - euclidean distance between two volumetric images A and B " << std::endl
310  << std::endl << "Basic usage: "<< std::endl
311  << "\t volShapeMetrics --volA <volAFilename> --volB <volBFilename> "<< std::endl
312  << general_opt << "\n"
313  << "Typical use :\n volShapeMetrics -a imageA.vol --aMin 128 --aMax 255 -b imageB.vol --bMin 128 --bMax 255 --distancesFromBnotInAOnly \n" ;
314 
315  return 0;
316  }
317 
318  if(! vm.count("volA")||! vm.count("volB"))
319  {
320  trace.error() << " The two volume filename are needed to be defined" << endl;
321  return 0;
322  }
323 
324  std::string volAFilename = vm["volA"].as<std::string>();
325  std::string volBFilename = vm["volB"].as<std::string>();
326 
327  int aMin = vm["aMin"].as<int>();
328  int aMax = vm["aMax"].as<int>();
329  int bMin = vm["bMin"].as<int>();
330  int bMax = vm["bMax"].as<int>();
331 
332  Image3D imageA = GenericReader<Image3D>::import(volAFilename);
333  Image3D imageB = GenericReader<Image3D>::import(volBFilename);
334 
335 
336  std::cout << "# Shape comparisons (generated with volShapeMetrics) given with the reference shape A: "<< volAFilename
337  << " (defined with threshold min: " << aMin << " and max: " << aMax << " )"<< endl
338  << "# and with the compared shape B: "<< volBFilename << " (defined with threshold min: " << bMin << " and max: " << bMax << " )"<< endl;
339  if(vm.count("displayTFstats")){
340  std::cout << "# #True_Positive #TrueNegative #FalsePositive #FalseNegative #TotalinA #TotalInB #TotalComplementOfRef #TotalComplementOfComp Precision Recall F-Mean ";
341  }else{
342  std::cout << "# #(Voxels of B in A) #(Voxels of NOT in B and NOT in A) #(Voxels of B and NOT in A) #(Voxels of NOT in B and in A) #(Voxels in A) #(Voxels in B) #(Voxels not in A) #(Voxels not in B) ";
343  }
344 
345 
346  if(!vm.count("noDistanceComparisons")){
347  std::cout << " Max(MinDistance(shape B to shape A) Mean(MinDistance(shape B to shape A) Variance(MinDistance(shape B to shape A)) "
348  << " Mediane(MinDistance(shape B to shape A) Farthest point of B to A ";
349  if(vm.count("distancesFromBnotInAOnly")){
350  std::cout << "*** for parts of B which are not in A only ***" ;
351  }
352  }
353  std::cout << std::endl;
354 
355 
356  if(vm.count("exportSDP")){
357  std::vector<Point> voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA;
358  if(vm.count("displayTFstats")){
359  getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, true, voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA, true);
360  exportSetofPoints("truePos.sdp", voxelsBinA);
361  exportSetofPoints("trueNeg.sdp", voxelsNotInBNotInA);
362  exportSetofPoints("falsePos.sdp", voxelsBNotInA);
363  exportSetofPoints("falseNeg.sdp", voxelsNotInBInA);
364  }else{
365  std::vector<Point> voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA;
366  getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, true, voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA, false);
367  exportSetofPoints("inBinA.sdp", voxelsBinA);
368  exportSetofPoints("notinBnotinA.sdp", voxelsNotInBNotInA);
369  exportSetofPoints("inBnotinA.sdp", voxelsBNotInA);
370  exportSetofPoints("notinBinA.sdp", voxelsNotInBInA);
371  }
372  }else{
373  getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, vm.count("displayTFstats"));
374  }
375 
376 
377  if(!vm.count("noDistanceComparisons")){
378  trace.info() << "Computing Distance Map stats ...";
379  Statistic<double> statDistances(true);
380  Point ptMax;
381  getStatsFromDistanceMap(statDistances, imageA, aMin, aMax, imageB, bMin, bMax, vm.count("statsFromFalsePosOnly"), &ptMax );
382  std::cout << " " << statDistances.max() << " " << statDistances.mean() << " " << statDistances.variance() << " "<< statDistances.median() << " " << ptMax[0] << " " << ptMax[1] << " " << ptMax[2] << std::endl;
383 
384  trace.info() << " [done] " << std::endl;
385  }
386 
387  return 1;
388 
389 }
STL namespace.
void addValue(Quantity v)
Trace trace(traceWriterTerm)
std::ostream & info()
const Domain & domain() const
typename Self::Point Point
std::vector< Value >::const_iterator ConstIterator
std::ostream & error()
Container::const_iterator ConstIterator