|
29 | 29 | import java.util.ArrayList; |
30 | 30 | import java.util.Collection; |
31 | 31 | import java.util.List; |
| 32 | +import java.util.stream.Stream; |
32 | 33 |
|
| 34 | +import ij.IJ; |
33 | 35 | import ij.ImagePlus; |
34 | 36 | import ij.ImageStack; |
35 | 37 | import ij.measure.Calibration; |
|
65 | 67 | */ |
66 | 68 | public class MorphometricFeatures3D extends AlgoStub |
67 | 69 | { |
68 | | - /** |
69 | | - * An enumeration used to identify which features have to be |
70 | | - * computed by the MorphometricFeatures3D class. |
71 | | - */ |
72 | | - public enum Feature |
73 | | - { |
74 | | - /** The number of voxels that compose the region.*/ |
75 | | - VOXEL_COUNT("Voxel_Count"), |
76 | | - /** The volume occupied by the region, as the number of voxels multiplied by image resolution.*/ |
77 | | - VOLUME("Volume"), |
78 | | - /** The surface area of the region (measured by Crofton formula) .*/ |
79 | | - SURFACE_AREA("Surface_Area"), |
80 | | - /** The mean breadth, proportional to the integral of mean curvature .*/ |
81 | | - MEAN_BREADTH("Mean_Breadth"), |
82 | | - /** The Euler number of the region, to quantify its topology.*/ |
83 | | - EULER_NUMBER("Euler_Number"), |
84 | | - /** The sphericity, as normalized ratio of powers of volume and surface area.*/ |
85 | | - SPHERICITY("Sphericity"), |
86 | | - /** The bounding box along each dimension.*/ |
87 | | - BOUNDING_BOX("Bounding_Box"), |
88 | | - /** The centroid.*/ |
89 | | - CENTROID("Centroid"), |
90 | | - /** The equivalent ellipsoid with same moments up to the second order as the region.*/ |
91 | | - EQUIVALENT_ELLIPSOID("Equivalent_Ellipsoid"), |
92 | | - /** The elongation factor of the equivalent ellipsoid.*/ |
93 | | - ELLIPSOID_ELONGATIONS("Ellipsoid_Elong."), |
94 | | - /** The radius of the largest inscribed ball.*/ |
95 | | - MAX_INSCRIBED_BALL("Max._Inscribed_Ball"); |
96 | | - |
97 | | - String label; |
98 | | - |
99 | | - private Feature(String label) |
100 | | - { |
101 | | - this.label = label; |
102 | | - } |
103 | | - |
104 | | - /** |
| 70 | + /** |
| 71 | + * An enumeration used to identify which features have to be computed by the |
| 72 | + * MorphometricFeatures3D class. |
| 73 | + */ |
| 74 | + public enum Feature |
| 75 | + { |
| 76 | + /** The number of voxels that compose the region. */ |
| 77 | + VOXEL_COUNT("Voxel_Count"), |
| 78 | + /** |
| 79 | + * The volume occupied by the region, as the number of voxels multiplied |
| 80 | + * by image resolution. |
| 81 | + */ |
| 82 | + VOLUME("Volume"), |
| 83 | + /** The surface area of the region (measured by Crofton formula) . */ |
| 84 | + SURFACE_AREA("Surface_Area"), |
| 85 | + /** |
| 86 | + * The mean breadth, proportional to the integral of mean curvature . |
| 87 | + */ |
| 88 | + MEAN_BREADTH("Mean_Breadth"), |
| 89 | + /** The Euler number of the region, to quantify its topology. */ |
| 90 | + EULER_NUMBER("Euler_Number"), |
| 91 | + /** |
| 92 | + * The sphericity, as normalized ratio of powers of volume and surface |
| 93 | + * area. |
| 94 | + */ |
| 95 | + SPHERICITY("Sphericity"), |
| 96 | + /** The bounding box along each dimension. */ |
| 97 | + BOUNDING_BOX("Bounding_Box"), |
| 98 | + /** The centroid. */ |
| 99 | + CENTROID("Centroid"), |
| 100 | + /** |
| 101 | + * The equivalent ellipsoid with same moments up to the second order as |
| 102 | + * the region. |
| 103 | + */ |
| 104 | + EQUIVALENT_ELLIPSOID("Equivalent_Ellipsoid"), |
| 105 | + /** The elongation factor of the equivalent ellipsoid. */ |
| 106 | + ELLIPSOID_ELONGATIONS("Ellipsoid_Elong."), |
| 107 | + /** The radius of the largest inscribed ball. */ |
| 108 | + MAX_INSCRIBED_BALL("Max._Inscribed_Ball"); |
| 109 | + |
| 110 | + String label; |
| 111 | + |
| 112 | + private Feature(String label) |
| 113 | + { |
| 114 | + this.label = label; |
| 115 | + } |
| 116 | + |
| 117 | + /** |
105 | 118 | * Returns the label associated to this feature. |
106 | 119 | * |
107 | 120 | * @return the label associated to this feature. |
108 | 121 | */ |
109 | | - @Override |
110 | | - public String toString() |
111 | | - { |
112 | | - return this.label; |
113 | | - } }; |
114 | | - |
115 | | - |
| 122 | + @Override |
| 123 | + public String toString() |
| 124 | + { |
| 125 | + return this.label; |
| 126 | + } |
| 127 | + }; |
| 128 | + |
| 129 | + |
116 | 130 | // ==================================================== |
117 | 131 | // Inner fields |
118 | 132 |
|
@@ -396,122 +410,134 @@ private ResultsTable populateTable(Results results) |
396 | 410 | table.addLabel("" + results.labels[i]); |
397 | 411 | } |
398 | 412 |
|
399 | | - if (features.contains(VOXEL_COUNT)) |
400 | | - { |
401 | | - addColumnToTable(table, "VoxelCount", results.voxelCounts); |
402 | | - } |
403 | | - |
404 | | - if (features.contains(VOLUME)) |
405 | | - { |
406 | | - for (int i = 0; i < nLabels; i++) |
407 | | - { |
408 | | - table.setValue("Volume", i, results.intrinsicVolumes[i].volume); |
409 | | - } |
410 | | - } |
411 | | - |
412 | | - if (features.contains(SURFACE_AREA)) |
413 | | - { |
414 | | - for (int i = 0; i < nLabels; i++) |
415 | | - { |
416 | | - table.setValue("SurfaceArea", i, results.intrinsicVolumes[i].surfaceArea); |
417 | | - } |
418 | | - } |
419 | | - |
420 | | - if (features.contains(SPHERICITY)) |
421 | | - { |
422 | | - double[] sphericities = new double[nLabels]; |
423 | | - for (int i = 0; i < nLabels; i++) |
424 | | - { |
425 | | - sphericities[i] = results.intrinsicVolumes[i].sphericity(); |
426 | | - } |
427 | | - addColumnToTable(table, "Sphericity", sphericities); |
428 | | - } |
429 | | - |
430 | | - if (features.contains(MEAN_BREADTH)) |
431 | | - { |
432 | | - for (int i = 0; i < nLabels; i++) |
433 | | - { |
434 | | - table.setValue("MeanBreadth", i, results.intrinsicVolumes[i].meanBreadth); |
435 | | - } |
436 | | - } |
437 | | - |
438 | | - if (features.contains(EULER_NUMBER)) |
439 | | - { |
440 | | - for (int i = 0; i < nLabels; i++) |
441 | | - { |
442 | | - table.setValue("EulerNumber", i, results.intrinsicVolumes[i].eulerNumber); |
443 | | - } |
444 | | - } |
445 | | - |
446 | | - if (features.contains(BOUNDING_BOX)) |
447 | | - { |
448 | | - for (int i = 0; i < nLabels; i++) |
449 | | - { |
450 | | - Box3D box = results.boundingBoxes[i]; |
451 | | - table.setValue("Box.X.Min", i, box.getXMin()); |
452 | | - table.setValue("Box.X.Max", i, box.getXMax()); |
453 | | - table.setValue("Box.Y.Min", i, box.getYMin()); |
454 | | - table.setValue("Box.Y.Max", i, box.getYMax()); |
455 | | - table.setValue("Box.Z.Min", i, box.getZMin()); |
456 | | - table.setValue("Box.Z.Max", i, box.getZMax()); |
457 | | - } |
458 | | - } |
459 | | - |
460 | | - if (features.contains(CENTROID)) |
461 | | - { |
462 | | - for (int i = 0; i < nLabels; i++) |
463 | | - { |
464 | | - Point3D center = results.centroids[i]; |
465 | | - table.setValue("Centroid.X", i, center.getX()); |
466 | | - table.setValue("Centroid.Y", i, center.getY()); |
467 | | - table.setValue("Centroid.Z", i, center.getZ()); |
468 | | - } |
469 | | - } |
470 | | - |
471 | | - if (features.contains(EQUIVALENT_ELLIPSOID)) |
472 | | - { |
473 | | - for (int i = 0; i < nLabels; i++) |
474 | | - { |
475 | | - Ellipsoid elli = results.ellipsoids[i]; |
476 | | - Point3D center = elli.center(); |
477 | | - table.setValue("Elli.Center.X", i, center.getX()); |
478 | | - table.setValue("Elli.Center.Y", i, center.getY()); |
479 | | - table.setValue("Elli.Center.Z", i, center.getZ()); |
480 | | - table.setValue("Elli.Radius1", i, elli.radius1()); |
481 | | - table.setValue("Elli.Radius2", i, elli.radius2()); |
482 | | - table.setValue("Elli.Radius3", i, elli.radius3()); |
483 | | - table.setValue("Elli.Azim", i, elli.phi()); |
484 | | - table.setValue("Elli.Elev", i, elli.theta()); |
485 | | - table.setValue("Elli.Roll", i, elli.psi()); |
486 | | - } |
487 | | - } |
488 | | - |
489 | | - if (features.contains(ELLIPSOID_ELONGATIONS)) |
490 | | - { |
491 | | - for (int i = 0; i < nLabels; i++) |
492 | | - { |
493 | | - double[] elongs = results.ellipsoidElongations[i]; |
494 | | - table.setValue("Elli.R1/R2", i, elongs[0]); |
495 | | - table.setValue("Elli.R1/R3", i, elongs[1]); |
496 | | - table.setValue("Elli.R2/R3", i, elongs[2]); |
497 | | - } |
498 | | - } |
499 | | - |
500 | | - if (features.contains(MAX_INSCRIBED_BALL)) |
501 | | - { |
502 | | - for (int i = 0; i < nLabels; i++) |
503 | | - { |
504 | | - Point3D center = results.inscribedBalls[i].center(); |
505 | | - table.setValue("InscrBall.Center.X", i, center.getX()); |
506 | | - table.setValue("InscrBall.Center.Y", i, center.getY()); |
507 | | - table.setValue("InscrBall.Center.Z", i, center.getZ()); |
508 | | - table.setValue("InscrBall.Radius", i, results.inscribedBalls[i].radius()); |
509 | | - } |
510 | | - } |
511 | | - |
512 | | - this.fireStatusChanged(this, ""); |
513 | | - return table; |
514 | | - } |
| 413 | + /** |
| 414 | + * Iterate over features, in the order they are specified. |
| 415 | + */ |
| 416 | + for (Feature feature : features) |
| 417 | + { |
| 418 | + switch (feature) |
| 419 | + { |
| 420 | + case VOXEL_COUNT: |
| 421 | + { |
| 422 | + addColumnToTable(table, "VoxelCount", results.voxelCounts); |
| 423 | + break; |
| 424 | + } |
| 425 | + case VOLUME: |
| 426 | + { |
| 427 | + double[] volumes = Stream.of(results.intrinsicVolumes) |
| 428 | + .mapToDouble(res -> res.volume) |
| 429 | + .toArray(); |
| 430 | + addColumnToTable(table, "Volume", volumes); |
| 431 | + break; |
| 432 | + } |
| 433 | + case SURFACE_AREA: |
| 434 | + { |
| 435 | + double[] surfaces = Stream.of(results.intrinsicVolumes) |
| 436 | + .mapToDouble(res -> res.surfaceArea) |
| 437 | + .toArray(); |
| 438 | + addColumnToTable(table, "SurfaceArea", surfaces); |
| 439 | + break; |
| 440 | + } |
| 441 | + case MEAN_BREADTH: |
| 442 | + { |
| 443 | + double[] meanBreadths = Stream.of(results.intrinsicVolumes) |
| 444 | + .mapToDouble(res -> res.meanBreadth) |
| 445 | + .toArray(); |
| 446 | + addColumnToTable(table, "MeanBreadth", meanBreadths); |
| 447 | + break; |
| 448 | + } |
| 449 | + case SPHERICITY: |
| 450 | + { |
| 451 | + double[] sphericites = Stream.of(results.intrinsicVolumes) |
| 452 | + .mapToDouble(res -> res.sphericity()) |
| 453 | + .toArray(); |
| 454 | + addColumnToTable(table, "Sphericity", sphericites); |
| 455 | + break; |
| 456 | + } |
| 457 | + case BOUNDING_BOX: |
| 458 | + { |
| 459 | + for (int i = 0; i < nLabels; i++) |
| 460 | + { |
| 461 | + Box3D box = results.boundingBoxes[i]; |
| 462 | + table.setValue("Box.X.Min", i, box.getXMin()); |
| 463 | + table.setValue("Box.X.Max", i, box.getXMax()); |
| 464 | + table.setValue("Box.Y.Min", i, box.getYMin()); |
| 465 | + table.setValue("Box.Y.Max", i, box.getYMax()); |
| 466 | + table.setValue("Box.Z.Min", i, box.getZMin()); |
| 467 | + table.setValue("Box.Z.Max", i, box.getZMax()); |
| 468 | + } |
| 469 | + break; |
| 470 | + } |
| 471 | + case CENTROID: |
| 472 | + { |
| 473 | + for (int i = 0; i < nLabels; i++) |
| 474 | + { |
| 475 | + Point3D center = results.centroids[i]; |
| 476 | + table.setValue("Centroid.X", i, center.getX()); |
| 477 | + table.setValue("Centroid.Y", i, center.getY()); |
| 478 | + table.setValue("Centroid.Z", i, center.getZ()); |
| 479 | + } |
| 480 | + break; |
| 481 | + } |
| 482 | + case EQUIVALENT_ELLIPSOID: |
| 483 | + { |
| 484 | + for (int i = 0; i < nLabels; i++) |
| 485 | + { |
| 486 | + Ellipsoid elli = results.ellipsoids[i]; |
| 487 | + Point3D center = elli.center(); |
| 488 | + table.setValue("Elli.Center.X", i, center.getX()); |
| 489 | + table.setValue("Elli.Center.Y", i, center.getY()); |
| 490 | + table.setValue("Elli.Center.Z", i, center.getZ()); |
| 491 | + table.setValue("Elli.Radius1", i, elli.radius1()); |
| 492 | + table.setValue("Elli.Radius2", i, elli.radius2()); |
| 493 | + table.setValue("Elli.Radius3", i, elli.radius3()); |
| 494 | + table.setValue("Elli.Azim", i, elli.phi()); |
| 495 | + table.setValue("Elli.Elev", i, elli.theta()); |
| 496 | + table.setValue("Elli.Roll", i, elli.psi()); |
| 497 | + } |
| 498 | + break; |
| 499 | + } |
| 500 | + case ELLIPSOID_ELONGATIONS: |
| 501 | + { |
| 502 | + for (int i = 0; i < nLabels; i++) |
| 503 | + { |
| 504 | + double[] elongs = results.ellipsoidElongations[i]; |
| 505 | + table.setValue("Elli.R1/R2", i, elongs[0]); |
| 506 | + table.setValue("Elli.R1/R3", i, elongs[1]); |
| 507 | + table.setValue("Elli.R2/R3", i, elongs[2]); |
| 508 | + } |
| 509 | + break; |
| 510 | + } |
| 511 | + case MAX_INSCRIBED_BALL: |
| 512 | + { |
| 513 | + for (int i = 0; i < nLabels; i++) |
| 514 | + { |
| 515 | + Point3D center = results.inscribedBalls[i].center(); |
| 516 | + table.setValue("InscrBall.Center.X", i, center.getX()); |
| 517 | + table.setValue("InscrBall.Center.Y", i, center.getY()); |
| 518 | + table.setValue("InscrBall.Center.Z", i, center.getZ()); |
| 519 | + table.setValue("InscrBall.Radius", i, results.inscribedBalls[i].radius()); |
| 520 | + } |
| 521 | + break; |
| 522 | + } |
| 523 | + case EULER_NUMBER: |
| 524 | + { |
| 525 | + double[] eulerNumbers = Stream.of(results.intrinsicVolumes) |
| 526 | + .mapToDouble(res -> res.eulerNumber) |
| 527 | + .toArray(); |
| 528 | + addColumnToTable(table, "EulerNumber", eulerNumbers); |
| 529 | + break; |
| 530 | + } |
| 531 | + default: |
| 532 | + { |
| 533 | + IJ.log("Unknown feature: " + feature.toString()); |
| 534 | + } |
| 535 | + } |
| 536 | + } |
| 537 | + |
| 538 | + this.fireStatusChanged(this, ""); |
| 539 | + return table; |
| 540 | + } |
515 | 541 |
|
516 | 542 | private static final void addColumnToTable(ResultsTable table, |
517 | 543 | String colName, double[] values) |
|
0 commit comments