DGtalTools 2.1.0
Loading...
Searching...
No Matches
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 "CLI11.hpp"
41
42#include <limits>
43
44using namespace std;
45using namespace DGtal;
46using namespace Z3i;
47
48
113typedef ImageContainerBySTLVector < Z3i::Domain, int > Image3D;
114typedef ImageContainerBySTLVector < Z2i::Domain, int > Image2D;
115
116
117bool
118isDiff(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
119 int bMin, int bMax, const Point &pt){
120 bool isRefOn = (imageA(pt)<= aMax) && (imageA(pt) >= aMin);
121 bool isCompOn = (imageB(pt)<= bMax) && (imageB(pt) >= bMin);
122 return ((!isRefOn && isCompOn) || (isRefOn && !isCompOn));
123}
124
125
126
127void
128getStatsFromDistanceMap(Statistic<double> & stats, const Image3D &imageA, int aMin, int aMax,
129 const Image3D & imageB, int bMin, int bMax,
130 bool statOnFalsePositiveOnly=false, Point *ptMax=0){
131
132 // Get the digital set from ref image by computing the surface (use -1 and +1 since the interval of append function are open)
133 Z3i::DigitalSet set3dRef (imageA.domain());
134 SetFromImage<Z3i::DigitalSet>::append<Image3D>(set3dRef, imageA, aMin-1,aMax);
135 typedef functors::NotPointPredicate<Z3i::DigitalSet> NegPredicate;
136
137
138 // Applying the distance transform on the digital surface of the set:
139 typedef DistanceTransformation<Z3i::Space, NegPredicate, Z3i::L2Metric> DTL2;
140 const NegPredicate aPredicate( set3dRef );
141 DTL2 dtL2( imageA.domain(), aPredicate, Z3i::l2Metric );
142
143 // Get the set of point of imageB: (use -1 and +1 since the interval of append function are open)
144 Z3i::DigitalSet set3dComp (imageB.domain());
145 SetFromImage<Z3i::DigitalSet>::append<Image3D>(set3dComp, imageB, bMin-1, bMax);
146
147 unsigned int nbAdded=0;
148 double maxDist=0;
149 //Applying stats from the set to be compared (from imageB)
150 for(Z3i::DigitalSet::ConstIterator it= set3dComp.begin(); it!= set3dComp.end(); ++it){
151 if((!statOnFalsePositiveOnly) || (isDiff(imageA, aMin, aMax, imageB, bMin, bMax, *it))){
152 DTL2::Value distance = dtL2(*it);
153 stats.addValue(distance);
154 nbAdded++;
155 if(maxDist<distance){
156 maxDist=distance;
157 if(ptMax!=0){
158 (*ptMax)[0]=(*it)[0];
159 (*ptMax)[1]=(*it)[1];
160 (*ptMax)[2]=(*it)[2];
161 }
162 }
163 }
164 }
165
166 if(nbAdded==0)
167 trace.error() << "No point added to statistics, will failed..." << endl;
168}
169
170
171void
172getVoxelsStats(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
173 int bMin, int bMax, bool exportStatVoxels, std::vector<Point> &vectPtBinA,
174 std::vector<Point> &vectPtCompBinCompA, std::vector<Point> &vectPtBnotInA,
175 std::vector<Point> &vectPtnotBInA, bool precisionRecallFMean ){
176 int numBinA = 0; // true positif with A as ref shape.
177 int numCompBinCompA = 0; // true neg with A as ref shape.
178 int numBnotInA = 0; // false pos with A as ref shape
179 int numNotBinA = 0; // false neg with A as ref shape
180 int numTotalInA = 0; // total pos in reference shape
181 int numTotalInB = 0; // total pos in compared shape
182 int numTotalinCompA = 0; //total in complement A
183 int numTotalinCompB = 0; //total in complement B
184
185 for(Image3D::Domain::ConstIterator it = imageA.domain().begin(); it!=imageA.domain().end(); it++){
186 // voxels in A
187 if(imageA(*it) <= aMax && imageA(*it) >= aMin){
188 numTotalInA++;
189 //voxels in B
190 if(imageB(*it) <= bMax && imageB(*it) >= bMin){
191 numTotalInB++;
192 numBinA++;
193 if(exportStatVoxels) vectPtBinA.push_back(*it);
194 }else{
195 numTotalinCompB++;
196 numNotBinA++;
197 if(exportStatVoxels) vectPtnotBInA.push_back(*it);
198 }
199 // voxels outside A
200 }else{
201 numTotalinCompA++;
202 // voxels in B
203 if(imageB(*it) <= bMax && imageB(*it) >= bMin){
204 numBnotInA++;
205 numTotalInB++;
206 if(exportStatVoxels) vectPtBnotInA.push_back(*it);
207 }else{
208 numTotalinCompB++;
209 numCompBinCompA++;
210 if(exportStatVoxels) vectPtCompBinCompA.push_back(*it);
211 }
212 }
213 }
214
215 std::cout << numBinA << " " << numCompBinCompA << " " << numBnotInA
216 << " " << numNotBinA << " " << numTotalInA << " "
217 << numTotalInB << " " << numTotalinCompA << " " << numTotalinCompB;
218 if(precisionRecallFMean){
219 double precision = (double)numBinA/(numBinA + numBnotInA);
220 double recall = (double)numBinA/(numBinA+numNotBinA);
221 double fmean = (2.0*precision*recall)/(precision+recall);
222 std::cout << " " << precision<< " " << recall << " " << fmean ;
223 }
224}
225
226
227// total ref: True Positive, True Negative, False Positive, False Negative
228void
229getVoxelsStats(const Image3D &imageA, int aMin, int aMax, const Image3D &imageB,
230 int bMin, int bMax, bool precisionRecallFMean ){
231 std::vector<Point> v1, v2, v3, v4;
232 return getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, false, v1, v2, v3, v4, precisionRecallFMean);
233}
234
235
236
237
238
239void
240exportSetofPoints(string filename, std::vector<Point> aVectPoint){
241 std::ofstream ofs;
242 ofs.open(filename.c_str(), std::ofstream::out );
243 ofs<< "# Set of 3d points with format: x y z" << std::endl;
244 for (unsigned int i =0; i< aVectPoint.size(); i++){
245 ofs << aVectPoint.at(i)[0] << " " << aVectPoint.at(i)[1] << " "<< aVectPoint.at(i)[2] << std::endl;
246 }
247 ofs.close();
248}
249
250
251
252int main(int argc, char**argv)
253{
254
255
256 // parse command line ----------------------------------------------
257 // A = ref
258 // B= comp
259 // parse command line using CLI ----------------------------------------------
260 CLI::App app;
261 std::string volAFilename;
262 std::string volBFilename;
263
264 int aMin {0};
265 int aMax {128} ;
266 int bMin {0};
267 int bMax {128};
268 bool noDistanceComparisons {false};
269 bool distancesFromBnotInAOnly {false};
270 bool displayTFstats {false};
271 bool exportSDP {false};
272
273 app.description("Apply shape measures for comparing two volumetric images A and B (shape defined from thresholds).\n It can compute: \n - voxel count from voxel partition (number of voxel from (B-A), (A-B) ...): usefull to determine classical statistics like false positive related stats.\n - euclidean distance between two volumetric images A and B\n Basic usage: \t volShapeMetrics --volA <volAFilename> --volB <volBFilename>\nTypical use :\n volShapeMetrics -a imageA.vol --aMin 128 --aMax 255 -b imageB.vol --bMin 128 --bMax 255 --distancesFromBnotInAOnly \n");
274
275
276
277 app.add_option("-a,--volA,1", volAFilename, "Input filename of volume A (vol format, and other pgm3d can also be used)." )
278 ->required()
279 ->check(CLI::ExistingFile);
280
281 app.add_option("-b,--volB,2", volBFilename, "Input filename of volume B (vol format, and other pgm3d can also be used).")
282 ->required()
283 ->check(CLI::ExistingFile);
284 app.add_option("--aMin",aMin, "min threshold for a voxel to be considered as belonging to the object of volume A. (default 0)");
285 app.add_option("--aMax",aMax, "max threshold for a voxel to be considered as belonging to the object of volume A. (default 128)");
286
287 app.add_option("--bMin",bMin, "min threshold for a voxel to be considered as belonging to the object of volume B. (default 0)");
288 app.add_option("--bMax",bMax, "max threshold for a voxel to be considered as belonging to the object of volume B. (default 128)");
289
290 app.add_flag("--noDistanceComparisons", noDistanceComparisons, "to avoid to apply distance map computation if the distance comparaison are not needed.");
291
292 app.add_flag("--distancesFromBnotInAOnly",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).");
293
294 app.add_flag("--displayTFstats", 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.");
295 app.add_flag("--exportSDP", 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)).");
296
297
298 app.get_formatter()->column_width(40);
299 CLI11_PARSE(app, argc, argv);
300 // END parse command line using CLI ----------------------------------------------
301
302
303 Image3D imageA = GenericReader<Image3D>::import(volAFilename);
304 Image3D imageB = GenericReader<Image3D>::import(volBFilename);
305
306 std::cout << "# Shape comparisons (generated with volShapeMetrics) given with the reference shape A: "<< volAFilename
307 << " (defined with threshold min: " << aMin << " and max: " << aMax << " )"<< endl
308 << "# and with the compared shape B: "<< volBFilename << " (defined with threshold min: " << bMin << " and max: " << bMax << " )"<< endl;
309 if(displayTFstats){
310 std::cout << "# #True_Positive #TrueNegative #FalsePositive #FalseNegative #TotalinA #TotalInB #TotalComplementOfRef #TotalComplementOfComp Precision Recall F-Mean ";
311 }else{
312 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) ";
313 }
314
315 if(!noDistanceComparisons){
316 std::cout << " Max(MinDistance(shape B to shape A) Mean(MinDistance(shape B to shape A) Variance(MinDistance(shape B to shape A)) "
317 << " Mediane(MinDistance(shape B to shape A) Farthest point of B to A ";
318 if(distancesFromBnotInAOnly){
319 std::cout << "*** for parts of B which are not in A only ***" ;
320 }
321 }
322 std::cout << std::endl;
323
324 if(exportSDP){
325 std::vector<Point> voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA;
326 if(displayTFstats){
327 getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, true, voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA, true);
328 exportSetofPoints("truePos.sdp", voxelsBinA);
329 exportSetofPoints("trueNeg.sdp", voxelsNotInBNotInA);
330 exportSetofPoints("falsePos.sdp", voxelsBNotInA);
331 exportSetofPoints("falseNeg.sdp", voxelsNotInBInA);
332 }else{
333 std::vector<Point> voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA;
334 getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, true, voxelsBinA, voxelsNotInBNotInA, voxelsBNotInA, voxelsNotInBInA, false);
335 exportSetofPoints("inBinA.sdp", voxelsBinA);
336 exportSetofPoints("notinBnotinA.sdp", voxelsNotInBNotInA);
337 exportSetofPoints("inBnotinA.sdp", voxelsBNotInA);
338 exportSetofPoints("notinBinA.sdp", voxelsNotInBInA);
339 }
340 }else{
341 getVoxelsStats(imageA, aMin, aMax, imageB, bMin, bMax, displayTFstats);
342 }
343
344 if(!noDistanceComparisons){
345 trace.info() << "Computing Distance Map stats ...";
346 Statistic<double> statDistances(true);
347 Point ptMax;
348 getStatsFromDistanceMap(statDistances, imageA, aMin, aMax, imageB, bMin, bMax, false, &ptMax );
349 std::cout << " " << statDistances.max() << " " << statDistances.mean() << " " << statDistances.variance() << " "<< statDistances.median() << " " << ptMax[0] << " " << ptMax[1] << " " << ptMax[2] << std::endl;
350
351 trace.info() << " [done] " << std::endl;
352 }
353
354 return 1;
355}
Definition ATu0v1.h:57