diff --git a/notebooks/2_convex_hull.ipynb b/notebooks/2_convex_hull.ipynb index f2c9a380..3a917c52 100644 --- a/notebooks/2_convex_hull.ipynb +++ b/notebooks/2_convex_hull.ipynb @@ -23,7 +23,7 @@ "metadata": {}, "source": [ "## Imports\n", - "We'll need to 1) import Python modules; 2) initalize ij from local Fiji installation, and 3) import all relevant SNT (Java) classes: [AllenCompartment](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/annotation/AllenCompartment.html), [AllenUtils](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/annotation/AllenUtils.html), [Annotation3D](https://javadoc.scijava.org/SNT/sc/fiji/snt/viewer/Annotation3D.html), [ConvexHull2D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/ConvexHull2D.html), [ConvexHull3D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/ConvexHull3D.html), [MouseLightLoader](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/io/MouseLightLoader.html), [PointInImage](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/util/PointInImage.html), [Tree](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/Tree.html), [TreeAnalyzer](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/TreeAnalyzer.html), [Viewer3D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/viewer/Viewer3D.html):" + "We'll need to 1) import Python modules; 2) initalize ij from local Fiji installation, and 3) import all relevant SNT (Java) classes: [AllenCompartment](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/annotation/AllenCompartment.html), [AllenUtils](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/annotation/AllenUtils.html), [Annotation3D](https://javadoc.scijava.org/SNT/sc/fiji/snt/viewer/Annotation3D.html), [ConvexHull2D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/ConvexHull2D.html), [ConvexHull3D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/ConvexHull3D.html), [MouseLightLoader](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/io/MouseLightLoader.html), [PointInImage](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/util/PointInImage.html), [Tree](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/Tree.html), [TreeStatistics](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/analysis/TreeStatistics.html), [Viewer3D](https://javadoc.scijava.org/SNT/index.html?sc/fiji/snt/viewer/Viewer3D.html):" ] }, { @@ -57,7 +57,7 @@ "NodeStatistics = jimport('sc.fiji.snt.analysis.NodeStatistics')\n", "PointInImage = jimport('sc.fiji.snt.util.PointInImage')\n", "Tree = jimport('sc.fiji.snt.Tree')\n", - "TreeAnalyzer = jimport('sc.fiji.snt.analysis.TreeAnalyzer')\n", + "TreeStatistics = jimport('sc.fiji.snt.analysis.TreeStatistics')\n", "Viewer2D = jimport('sc.fiji.snt.viewer.Viewer2D')\n", "Viewer3D = jimport('sc.fiji.snt.viewer.Viewer3D')" ] @@ -93,13 +93,13 @@ " of axon terminal nodes and returns a collection containing \n", " these nodes as well as the id of the relevant AllenCompartment\"\"\"\n", " \n", - " # Use TreeAnalyzer to extract the terminal nodes from the Tree.\n", + " # Use TreeStatistics to extract the terminal nodes from the Tree.\n", " # Instantiate a NodeStatistics instance and retrieve a list of the endpoints for\n", " # each target brain region (a BrainAnnotation) in a dictionary, where the keys are\n", " # the brain annotations. Since this neuron was fetched from the MouseLight database,\n", " # the annotations are instances of the AllenCompartment Class\n", " # https://morphonets.github.io/SNT/sc/fiji/snt/annotation/BrainAnnotation.html\n", - " tips = TreeAnalyzer(tree).getTips()\n", + " tips = TreeStatistics(tree).getTips()\n", " node_stats = NodeStatistics(tips)\n", " compartment_dict = node_stats.getAnnotatedNodes()\n", "\n", @@ -127,7 +127,7 @@ "source": [ "# Load the reconstruction from the MouseLight database, fetching just the axon\n", "tree_axon = get_axon('AA1044')\n", - "print(tree_axon.getLabel(), \":\", len(TreeAnalyzer(tree_axon).getTips()), \"terminals\")" + "print(tree_axon.getLabel(), \":\", len(TreeStatistics(tree_axon).getTips()), \"terminals\")" ] }, { diff --git a/notebooks/5_performance_testing.ipynb b/notebooks/5_performance_testing.ipynb index d26eba0b..deec25bc 100644 --- a/notebooks/5_performance_testing.ipynb +++ b/notebooks/5_performance_testing.ipynb @@ -42,7 +42,7 @@ "PointInImage = jimport('sc.fiji.snt.util.PointInImage')\n", "MouseLightLoader = jimport('sc.fiji.snt.io.MouseLightLoader')\n", "Tree = jimport('sc.fiji.snt.Tree')\n", - "TreeAnalyzer = jimport('sc.fiji.snt.analysis.TreeAnalyzer')\n", + "TreeStatistics = jimport('sc.fiji.snt.analysis.TreeStatistics')\n", "Color = jimport('org.scijava.util.Colors')\n", "Viewer = jimport('sc.fiji.snt.viewer.Viewer3D')" ] diff --git a/notebooks/7_timelapse_segmentation.ipynb b/notebooks/7_timelapse_segmentation.ipynb index 08e2d98c..cfea5c9d 100644 --- a/notebooks/7_timelapse_segmentation.ipynb +++ b/notebooks/7_timelapse_segmentation.ipynb @@ -342,7 +342,7 @@ "id": "52cc4ec2", "metadata": {}, "source": [ - "We can also measure [Trees](https://javadoc.scijava.org/SNT/sc/fiji/snt/Tree.html) with the [TreeAnalyzer](https://javadoc.scijava.org/SNT/sc/fiji/snt/analysis/TreeAnalyzer.html) and [MultiTreeStatistics](https://javadoc.scijava.org/SNT/sc/fiji/snt/analysis/MultiTreeStatistics.html) classes, and plot measurements as a function of time." + "We can also measure [Trees](https://javadoc.scijava.org/SNT/sc/fiji/snt/Tree.html) with [TreeStatistics](https://javadoc.scijava.org/SNT/sc/fiji/snt/analysis/TreeStatistics.html) and [MultiTreeStatistics](https://javadoc.scijava.org/SNT/sc/fiji/snt/analysis/MultiTreeStatistics.html) classes, and plot measurements as a function of time." ] }, { @@ -365,7 +365,7 @@ } ], "source": [ - "TreeAnalyzer = jimport(\"sc.fiji.snt.analysis.TreeAnalyzer\")\n", + "TreeStatistics = jimport(\"sc.fiji.snt.analysis.TreeStatistics\")\n", "MultiTreeStatistics = jimport(\"sc.fiji.snt.analysis.MultiTreeStatistics\")\n", "\n", "def plot_metric(root_nodes, metric, ax):\n", @@ -373,7 +373,7 @@ " for i, r in enumerate(roots):\n", " lineage_trees = lineage_to_trees(get_descendants(r))\n", " xs = np.arange(r.frame, r.frame + len(lineage_trees))\n", - " ys = [TreeAnalyzer(tree).getMetric(metric) for tree in lineage_trees]\n", + " ys = [TreeStatistics(tree).getMetric(metric) for tree in lineage_trees]\n", " ax.set_title(f\"{metric} vs Time\")\n", " ax.set_ylabel(f\"{metric}\")\n", " ax.set_xlabel(\"Frame\")\n", diff --git a/src/main/java/sc/fiji/snt/Path.java b/src/main/java/sc/fiji/snt/Path.java index 18659f1c..d9dd410a 100644 --- a/src/main/java/sc/fiji/snt/Path.java +++ b/src/main/java/sc/fiji/snt/Path.java @@ -3087,7 +3087,7 @@ protected boolean removeChangeListener(PathChangeListener listener) { return changeListeners.remove(listener); } -// FIXME: Implementing hasCode() and equals() breaks current TreeAnalyzer tests +// FIXME: Implementing hasCode() and equals() breaks current TreeStatistics tests // @Override // public int hashCode() { // return Objects.hash(id, points, name, order, swcType, treeLabel); diff --git a/src/main/java/sc/fiji/snt/PathManagerUI.java b/src/main/java/sc/fiji/snt/PathManagerUI.java index a8f153d3..a68f4286 100644 --- a/src/main/java/sc/fiji/snt/PathManagerUI.java +++ b/src/main/java/sc/fiji/snt/PathManagerUI.java @@ -1915,12 +1915,12 @@ private void removeOrReapplyDefaultTag(final Collection paths, final Strin } break; case "Extension Angle...": - case PathAnalyzer.PATH_EXT_ANGLE_XY: - case PathAnalyzer.PATH_EXT_ANGLE_XZ: - case PathAnalyzer.PATH_EXT_ANGLE_ZY: - case PathAnalyzer.PATH_EXT_ANGLE_REL_XY: - case PathAnalyzer.PATH_EXT_ANGLE_REL_XZ: - case PathAnalyzer.PATH_EXT_ANGLE_REL_ZY: + case PathStatistics.PATH_EXT_ANGLE_XY: + case PathStatistics.PATH_EXT_ANGLE_XZ: + case PathStatistics.PATH_EXT_ANGLE_ZY: + case PathStatistics.PATH_EXT_ANGLE_REL_XY: + case PathStatistics.PATH_EXT_ANGLE_REL_XZ: + case PathStatistics.PATH_EXT_ANGLE_REL_ZY: paths.forEach(p -> p.setName(p.getName().replaceAll(" ?\\[" + SYM_ANGLE + "\\d+.\\d+]|\\[" + SYM_ANGLE + "NaN]", ""))); if (reapply) { final boolean relative = cmd.toLowerCase().contains("rel"); diff --git a/src/main/java/sc/fiji/snt/SNTService.java b/src/main/java/sc/fiji/snt/SNTService.java index 3100d2ee..ed599ba3 100644 --- a/src/main/java/sc/fiji/snt/SNTService.java +++ b/src/main/java/sc/fiji/snt/SNTService.java @@ -49,7 +49,6 @@ import ij.ImagePlus; import sc.fiji.snt.analysis.PathProfiler; import sc.fiji.snt.analysis.SNTTable; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.graph.DirectedWeightedGraph; import sc.fiji.snt.gui.GuiUtils; @@ -340,15 +339,15 @@ public Tree getTree() { } /** - * Returns a {@link TreeAnalyzer} instance constructed from current Paths. + * Returns a {@link TreeStatistics} instance constructed from current Paths. * * @param selectedPathsOnly If true only selected paths will be considered - * @return the TreeAnalyzer instance + * @return the TreeStatistics instance * @throws UnsupportedOperationException if SNT is not running */ - public TreeAnalyzer getAnalyzer(final boolean selectedPathsOnly) { + public TreeStatistics getAnalyzer(final boolean selectedPathsOnly) { accessActiveInstance(false); - final TreeAnalyzer tAnalyzer = new TreeAnalyzer(getTree(selectedPathsOnly)); + final TreeStatistics tAnalyzer = new TreeStatistics(getTree(selectedPathsOnly)); tAnalyzer.setContext(getContext()); tAnalyzer.setTable(getTable(), PathManagerUI.TABLE_TITLE); return tAnalyzer; diff --git a/src/main/java/sc/fiji/snt/SNTUI.java b/src/main/java/sc/fiji/snt/SNTUI.java index f55522c4..d97742aa 100644 --- a/src/main/java/sc/fiji/snt/SNTUI.java +++ b/src/main/java/sc/fiji/snt/SNTUI.java @@ -42,7 +42,7 @@ import org.scijava.util.ColorRGB; import org.scijava.util.Types; import sc.fiji.snt.analysis.SNTTable; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.sholl.ShollUtils; import sc.fiji.snt.event.SNTEvent; import sc.fiji.snt.gui.*; @@ -2669,7 +2669,7 @@ private JMenuBar createMenuBar() { final Tree tree = getPathManager().getSingleTree(); if (tree == null) return; try { - final TreeAnalyzer ta = new TreeAnalyzer(tree); + final TreeStatistics ta = new TreeStatistics(tree); ta.setContext(plugin.getContext()); if (ta.getParsedTree().isEmpty()) { guiUtils.error("None of the selected paths could be measured."); diff --git a/src/main/java/sc/fiji/snt/Tree.java b/src/main/java/sc/fiji/snt/Tree.java index e15a3df1..3e06818e 100644 --- a/src/main/java/sc/fiji/snt/Tree.java +++ b/src/main/java/sc/fiji/snt/Tree.java @@ -37,7 +37,7 @@ import ij.ImagePlus; import ij.ImageStack; import ij.measure.Calibration; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.graph.DirectedWeightedGraph; import sc.fiji.snt.hyperpanes.MultiDThreePanes; import sc.fiji.snt.io.MouseLightLoader; @@ -1773,7 +1773,7 @@ else if (isZY) if (centroid) { refPath = tree.get(0).createPath(); refPath.addNode(tree.getRoot()); - final Set tips = new TreeAnalyzer(tree).getTips(); + final Set tips = new TreeStatistics(tree).getTips(); tips.remove(tree.getRoot()); if (!tips.isEmpty()) refPath.addNode(SNTPoint.average(tips)); } else { @@ -1814,7 +1814,7 @@ public static void main(final String[] args) { //final Tree tree = new Tree("/home/tferr/Downloads/test_swc.swc"); final Tree tree = new SNTService().demoTree("fractal"); tree.swapAxes(Z_AXIS, Y_AXIS); - TreeAnalyzer analyzer = new TreeAnalyzer(tree); + TreeStatistics analyzer = new TreeStatistics(tree); System.out.println("Creating graph..."); DirectedWeightedGraph graph = tree.getGraph(); @@ -1831,7 +1831,7 @@ public static void main(final String[] args) { MouseLightLoader loader = new MouseLightLoader("AA0174"); Tree refSubTree = loader.getTree("axon"); DirectedWeightedGraph refGraph = refSubTree.getGraph(); - TreeAnalyzer refAnalyzer = new TreeAnalyzer(refSubTree); + TreeStatistics refAnalyzer = new TreeStatistics(refSubTree); System.out.println("Creating Subtree..."); Tree aTree = tree.subTree("axon"); @@ -1846,7 +1846,7 @@ public static void main(final String[] args) { System.out.println("Avg time (all runs): " + SNTUtils.formatDouble(total % 1000 /nTimes, 4)); graph = aTree.getGraph(); - analyzer = new TreeAnalyzer(aTree); + analyzer = new TreeStatistics(aTree); System.out.println("Subtree properties:"); System.out.println("Label: \t\t" + aTree.getLabel()); System.out.println("nPaths: \t\t" + analyzer.getNPaths() + "/" + refAnalyzer.getNPaths()); @@ -1858,7 +1858,7 @@ public static void main(final String[] args) { System.out.println("Branches: \t\t" + analyzer.getBranches().size() + "/" + refAnalyzer.getBranches().size()); // Did the filtering affect original tree? - analyzer = new TreeAnalyzer(tree); + analyzer = new TreeStatistics(tree); graph = tree.getGraph(); System.out.println("\nOriginal Tree Before/After Filtering:"); System.out.println("Label: \t\t" + tree.getLabel()); diff --git a/src/main/java/sc/fiji/snt/analysis/AbstractConvexHull.java b/src/main/java/sc/fiji/snt/analysis/AbstractConvexHull.java index b31fdbba..9ecff191 100644 --- a/src/main/java/sc/fiji/snt/analysis/AbstractConvexHull.java +++ b/src/main/java/sc/fiji/snt/analysis/AbstractConvexHull.java @@ -33,6 +33,7 @@ import java.util.Collection; /** + * Parent class for convex hull analysis * @author Cameron Arshadi */ public abstract class AbstractConvexHull { diff --git a/src/main/java/sc/fiji/snt/analysis/ConvexHull3D.java b/src/main/java/sc/fiji/snt/analysis/ConvexHull3D.java index e90d851e..9ed135cd 100644 --- a/src/main/java/sc/fiji/snt/analysis/ConvexHull3D.java +++ b/src/main/java/sc/fiji/snt/analysis/ConvexHull3D.java @@ -33,6 +33,7 @@ import java.util.Collection; /** + * Convex hull analysis in 3D. * @author Cameron Arshadi */ public class ConvexHull3D extends AbstractConvexHull { diff --git a/src/main/java/sc/fiji/snt/analysis/GroupedTreeStatistics.java b/src/main/java/sc/fiji/snt/analysis/GroupedTreeStatistics.java index 6ced5c0d..fe167ccf 100644 --- a/src/main/java/sc/fiji/snt/analysis/GroupedTreeStatistics.java +++ b/src/main/java/sc/fiji/snt/analysis/GroupedTreeStatistics.java @@ -832,7 +832,7 @@ void compute(final Collection annotations, final Collection 0; double value; switch (normFeature) { @@ -841,12 +841,12 @@ void compute(final Collection annotations, final Collection */ public class NodeStatistics { @@ -614,7 +614,7 @@ public static void main(final String[] args) { hist.annotate("Free text"); hist.show(); final MouseLightLoader loader = new MouseLightLoader("AA0001"); - final TreeAnalyzer analyzer = new TreeAnalyzer(loader.getTree("axon")); + final TreeStatistics analyzer = new TreeStatistics(loader.getTree("axon")); final NodeStatistics nStats = new NodeStatistics<>(analyzer.getTips()); final SNTChart plot = nStats.getAnnotatedFrequencyHistogram(6, "ratio", analyzer.tree); plot.annotateCategory("CA1", "Not showing"); diff --git a/src/main/java/sc/fiji/snt/analysis/PathAnalyzer.java b/src/main/java/sc/fiji/snt/analysis/PathStatistics.java similarity index 92% rename from src/main/java/sc/fiji/snt/analysis/PathAnalyzer.java rename to src/main/java/sc/fiji/snt/analysis/PathStatistics.java index 09d9468e..89cf9195 100644 --- a/src/main/java/sc/fiji/snt/analysis/PathAnalyzer.java +++ b/src/main/java/sc/fiji/snt/analysis/PathStatistics.java @@ -33,15 +33,15 @@ import sc.fiji.snt.TreeProperties; /* - * A flavor of TreeStatistics that does not do graph conversions, ensuring paths - * can be measured independently of their connectivity. + * A flavor of TreeStatistics that does not do graph conversions, ensuring paths can be measured independently of + * their connectivity. */ -public class PathAnalyzer extends TreeStatistics { +public class PathStatistics extends TreeStatistics { /** Flag for {@value #N_CHILDREN} analysis. */ public static final String N_CHILDREN = "No. of children"; - public PathAnalyzer(Collection paths, String label) { + public PathStatistics(Collection paths, String label) { super(new Tree(paths)); tree.setLabel(label); final String unit = paths.iterator().next().getCalibration().getUnit(); @@ -49,7 +49,7 @@ public PathAnalyzer(Collection paths, String label) { tree.getProperties().setProperty(TreeProperties.KEY_SPATIAL_UNIT, unit); } - public PathAnalyzer(final Path path) { + public PathStatistics(final Path path) { this(Collections.singleton(path), path.getName()); } @@ -64,10 +64,10 @@ public int getNBranches() { } @Override - public Number getMetric(final String metric) throws IllegalArgumentException { + public Number getMetric(final String metric) throws UnknownMetricException { if ("Path ID".equalsIgnoreCase(metric)) return (tree.size() == 1) ? tree.list().get(0).getID() : Double.NaN; - return getMetricInternal(getNormalizedMeasurement(metric)); + return super.getMetric(metric); } @Override @@ -185,7 +185,7 @@ public Number getMetric(final String metric, final Path path) throws UnknownMetr // we can recycle the logic behind #assembleStats(). Since, all // getBranches() related code has been (hopefully) overridden, this will // effectively retrieve the single-value metric for this path. - final PathAnalyzer analyzer = new PathAnalyzer(path); + final PathStatistics analyzer = new PathStatistics(path); final SummaryStatistics dummy = new SummaryStatistics(); analyzer.assembleStats(new StatisticsInstance(dummy), metric); return dummy.getMax(); // since N=1, same as the value itself @@ -199,7 +199,7 @@ public void measureIndividualPaths(final Collection metrics, final boole final int row = getNextRow(path.getName()); table.set(getCol("SWC Type(s)"), row, Path.getSWCtypeName(path.getSWCType(), true)); // plural heading for consistency with other commands measuringMetrics.forEach(metric -> { - table.set(getCol(metric), row, new PathAnalyzer(path).getMetric(metric, path)); + table.set(getCol(metric), row, new PathStatistics(path).getMetric(metric, path)); }); }); if (summarize && table instanceof SNTTable) { diff --git a/src/main/java/sc/fiji/snt/analysis/RoiConverter.java b/src/main/java/sc/fiji/snt/analysis/RoiConverter.java index 670fd570..b33831ce 100644 --- a/src/main/java/sc/fiji/snt/analysis/RoiConverter.java +++ b/src/main/java/sc/fiji/snt/analysis/RoiConverter.java @@ -56,7 +56,7 @@ * @see ROIExporterCmd * @author Tiago Ferreira */ -public class RoiConverter extends TreeAnalyzer { +public class RoiConverter { static { net.imagej.patcher.LegacyInjector.preinit(); } // required for _every_ class that imports ij. classes @@ -73,6 +73,7 @@ public class RoiConverter extends TreeAnalyzer { private final ImagePlus imp; private final boolean hyperstack; private final boolean twoD; + private final TreeStatistics tStats; /** * Instantiates a new Converter. Since an image has not been specified, C,Z,T @@ -81,7 +82,7 @@ public class RoiConverter extends TreeAnalyzer { * @param tree the Tree to be converted */ public RoiConverter(final Tree tree) { - super(tree); + tStats = new TreeStatistics(tree); imp = null; hyperstack = false; twoD = !tree.is3D(); @@ -117,7 +118,7 @@ public RoiConverter(final Collection paths, final ImagePlus imp) { * positions of converted nodes */ public RoiConverter(final Tree tree, final ImagePlus imp) { - super(tree); + tStats = new TreeStatistics(tree); this.imp = imp; hyperstack = imp.getNChannels() > 1 || imp.getNFrames() > 1; twoD = imp.getNSlices() == 1; @@ -131,7 +132,7 @@ public RoiConverter(final Tree tree, final ImagePlus imp) { */ public Overlay convertPaths(Overlay overlay) { if (overlay == null) overlay = new Overlay(); - return convertPaths(overlay, tree.list(), null); + return convertPaths(overlay, tStats.tree.list(), null); } /** @@ -226,12 +227,12 @@ private List getROIs(final Path path, final String basename) { * Converts all the tips associated with the parsed paths into * {@link ij.gui.PointRoi}s * - * @see TreeAnalyzer#getTips() + * @see TreeStatistics#getTips() * @param overlay the target overlay to hold converted point */ public void convertTips(Overlay overlay) { if (overlay == null) overlay = new Overlay(); - convertPoints(getTips(), overlay, Color.PINK, "EndPoint"); + convertPoints(tStats.getTips(), overlay, Color.PINK, "EndPoint"); } /** @@ -243,7 +244,7 @@ public void convertTips(Overlay overlay) { public void convertRoots(Overlay overlay) { if (overlay == null) overlay = new Overlay(); final Set roots = new HashSet<>(); - for (final Path p : tree.list()) { + for (final Path p : tStats.tree.list()) { if (p.isPrimary()) roots.add(p.getNode(0)); } @@ -265,21 +266,21 @@ public void convertPaths() throws IllegalArgumentException { public void convertInnerBranches(Overlay overlay) { if (overlay == null) overlay = new Overlay(); int lastIdx = overlay.size(); - convertPaths(overlay, getInnerBranches(), "InnerBranch"); + convertPaths(overlay, tStats.getInnerBranches(), "InnerBranch"); appendNumericSuffixToROIs(overlay, lastIdx, overlay.size()-1); } public void convertPrimaryBranches(Overlay overlay) { if (overlay == null) overlay = new Overlay(); int lastIdx = overlay.size(); - convertPaths(overlay, getPrimaryBranches(), "Prim.Branch"); + convertPaths(overlay, tStats.getPrimaryBranches(), "Prim.Branch"); appendNumericSuffixToROIs(overlay, lastIdx, overlay.size()-1); } public void convertTerminalBranches(Overlay overlay) { if (overlay == null) overlay = new Overlay(); int lastIdx = overlay.size(); - convertPaths(overlay, getTerminalBranches(), "Term.Branch"); + convertPaths(overlay, tStats.getTerminalBranches(), "Term.Branch"); appendNumericSuffixToROIs(overlay, lastIdx, overlay.size()-1); } @@ -355,12 +356,12 @@ private Overlay getImpOverlay() throws IllegalArgumentException { * Converts all the branch points associated with the parsed paths into * {@link ij.gui.PointRoi}s * - * @see TreeAnalyzer#getBranchPoints() + * @see TreeStatistics#getBranchPoints() * @param overlay the target overlay to hold converted point */ public void convertBranchPoints(Overlay overlay) { if (overlay == null) overlay = new Overlay(); - convertPoints(getBranchPoints(), overlay, Color.ORANGE, "BranchPoint"); + convertPoints(tStats.getBranchPoints(), overlay, Color.ORANGE, "BranchPoint"); } /** @@ -497,7 +498,7 @@ public SNTPointRoi() { // to points. It is an overhead and not required for 2D images // We should change the IJ1 API so that position can be assigned // without an image - imp = tree.getImpContainer(exportPlane, 8); + imp = tStats.tree.getImpContainer(exportPlane, 8); } } diff --git a/src/main/java/sc/fiji/snt/analysis/ShollAnalyzer.java b/src/main/java/sc/fiji/snt/analysis/ShollAnalyzer.java index 7b44d404..547684e5 100644 --- a/src/main/java/sc/fiji/snt/analysis/ShollAnalyzer.java +++ b/src/main/java/sc/fiji/snt/analysis/ShollAnalyzer.java @@ -129,9 +129,9 @@ public ShollAnalyzer(final Tree tree) { nPrimaryBranches = -1; //infer from intersections at starting radius } - public ShollAnalyzer(final Tree tree, final TreeAnalyzer analyzer) { + public ShollAnalyzer(final Tree tree, final TreeStatistics statistics) { this(tree); - nPrimaryBranches = analyzer.getPrimaryPaths().size(); + nPrimaryBranches = statistics.getPrimaryPaths().size(); } /** diff --git a/src/main/java/sc/fiji/snt/analysis/TreeAnalyzer.java b/src/main/java/sc/fiji/snt/analysis/TreeAnalyzer.java index 3ff0fcf9..d65667e6 100644 --- a/src/main/java/sc/fiji/snt/analysis/TreeAnalyzer.java +++ b/src/main/java/sc/fiji/snt/analysis/TreeAnalyzer.java @@ -22,1356 +22,14 @@ package sc.fiji.snt.analysis; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.jgrapht.Graphs; -import org.jgrapht.traverse.DepthFirstIterator; -import org.scijava.app.StatusService; -import org.scijava.command.ContextCommand; -import org.scijava.display.Display; -import org.scijava.display.DisplayService; -import org.scijava.plugin.Parameter; -import org.scijava.table.DefaultGenericTable; - -import net.imagej.ImageJ; -import sc.fiji.snt.Path; -import sc.fiji.snt.SNTService; -import sc.fiji.snt.SNTUtils; import sc.fiji.snt.Tree; -import sc.fiji.snt.TreeProperties; -import sc.fiji.snt.analysis.graph.DirectedWeightedGraph; -import sc.fiji.snt.analysis.graph.DirectedWeightedSubgraph; -import sc.fiji.snt.analysis.graph.SWCWeightedEdge; -import sc.fiji.snt.annotation.BrainAnnotation; -import sc.fiji.snt.util.PointInImage; -import sc.fiji.snt.util.SWCPoint; - -/** - * Class for 'entry-level' analysis of a {@link Tree}s. For more sophisticated - * analysis have a look at {@link TreeStatistics} - * - * @author Tiago Ferreira - */ -public class TreeAnalyzer extends ContextCommand { - - @Parameter - protected StatusService statusService; - - @Parameter - protected DisplayService displayService; - - - protected Tree tree; - private Tree unfilteredTree; - - protected List primaryBranches; - protected List innerBranches; - protected List terminalBranches; - private Set joints; - protected Set tips; - - protected DefaultGenericTable table; - private String tableTitle; - private StrahlerAnalyzer sAnalyzer; - private ShollAnalyzer shllAnalyzer; - - private int fittedPathsCounter = 0; - private int unfilteredPathsFittedPathsCounter = 0; - - /** - * Instantiates a new Tree analyzer from a collection of paths. - * - * @param paths Collection of Paths to be analyzed. Note that null Paths are - * discarded. Also, when a Path has been fitted and - * {@link Path#getUseFitted()} is true, its fitted 'flavor' is used. - * @param label the label describing the path collection - * @see #getParsedTree() - */ - public TreeAnalyzer(final Collection paths, String label) { - tree = new Tree(paths); - tree.setLabel(label); - for (final Path p : tree.list()) { - // If fitted flavor of path exists use it instead - if (p.getUseFitted()) { - fittedPathsCounter++; - } - } - unfilteredPathsFittedPathsCounter = fittedPathsCounter; - } - - /** - * Instantiates a new Tree analyzer. - * - * @param tree the Tree to be analyzed. - */ - public TreeAnalyzer(final Tree tree) { - this.tree = tree; - unfilteredPathsFittedPathsCounter = 0; - fittedPathsCounter = 0; - } - - /** - * Restricts analysis to Paths sharing the specified SWC flag(s). - * - * @param types the allowed SWC flags (e.g., {@link Path#SWC_AXON}, etc.) - */ - public void restrictToSWCType(final int... types) { - initializeSnapshotTree(); - tree = tree.subTree(types); - } - - /** - * Ignores Paths sharing the specified SWC flag(s). - * - * @param types the SWC flags to be ignored (e.g., {@link Path#SWC_AXON}, - * etc.) - */ - public void ignoreSWCType(final int... types) { - initializeSnapshotTree(); - final ArrayList allowedTypes = Path.getSWCtypes(); - for (final int type : types) { - allowedTypes.remove(Integer.valueOf(type)); - } - tree = tree.subTree(allowedTypes.stream().mapToInt(i -> i).toArray()); - } - - /** - * Restricts analysis to Paths sharing the specified Path {@link Path#getOrder() - * order}(s). - * - * @param orders the allowed Path orders - */ - public void restrictToOrder(final int... orders) { - initializeSnapshotTree(); - final Iterator it = tree.list().iterator(); - while (it.hasNext()) { - final Path p = it.next(); - final boolean valid = Arrays.stream(orders).anyMatch(t -> t == p - .getOrder()); - if (!valid) { - updateFittedPathsCounter(p); - it.remove(); - } - } - } - - /** - * Restricts analysis to paths having the specified number of nodes. - * - * @param minSize the smallest number of nodes a path must have in order to be - * analyzed. Set it to -1 to disable minSize filtering - * @param maxSize the largest number of nodes a path must have in order to be - * analyzed. Set it to -1 to disable maxSize filtering - */ - public void restrictToSize(final int minSize, final int maxSize) { - initializeSnapshotTree(); - final Iterator it = tree.list().iterator(); - while (it.hasNext()) { - final Path p = it.next(); - final int size = p.size(); - if ((minSize > 0 && size < minSize) || (maxSize > 0 && size > maxSize)) { - updateFittedPathsCounter(p); - it.remove(); - } - } - } - - /** - * Restricts analysis to paths sharing the specified length range. - * - * @param lowerBound the smallest length a path must have in order to be - * analyzed. Set it to Double.NaN to disable lowerBound filtering - * @param upperBound the largest length a path must have in order to be - * analyzed. Set it to Double.NaN to disable upperBound filtering - */ - public void restrictToLength(final double lowerBound, - final double upperBound) - { - initializeSnapshotTree(); - final Iterator it = tree.list().iterator(); - while (it.hasNext()) { - final Path p = it.next(); - final double length = p.getLength(); - if (length < lowerBound || length > upperBound) { - updateFittedPathsCounter(p); - it.remove(); - } - } - } - - /** - * Restricts analysis to Paths containing the specified string in their name. - * - * @param pattern the string to search for - */ - public void restrictToNamePattern(final String pattern) { - initializeSnapshotTree(); - final Iterator it = tree.list().iterator(); - while (it.hasNext()) { - final Path p = it.next(); - if (!p.getName().contains(pattern)) { - updateFittedPathsCounter(p); - it.remove(); - } - } - } - - private void initializeSnapshotTree() { - if (unfilteredTree == null) { - unfilteredTree = new Tree(tree.list()); - unfilteredTree.setLabel(tree.getLabel()); - } - sAnalyzer = null; // reset Strahler analyzer - } - - /** - * Removes any filtering restrictions that may have been set. Once called, - * subsequent analysis will use all paths initially parsed by the constructor. - * Does nothing if no paths are currently being excluded from the analysis. - */ - public void resetRestrictions() { - if (unfilteredTree == null) return; // no filtering has occurred - tree.replaceAll(unfilteredTree.list()); - joints = null; - primaryBranches = null; - innerBranches = null; - terminalBranches = null; - tips = null; - sAnalyzer = null; - shllAnalyzer = null; - fittedPathsCounter = unfilteredPathsFittedPathsCounter; - } - - private void updateFittedPathsCounter(final Path filteredPath) { - if (fittedPathsCounter > 0 && filteredPath.isFittedVersionOfAnotherPath()) - fittedPathsCounter--; - } - - /** - * Returns the set of parsed Paths. - * - * @return the set of paths currently being considered for analysis. - * @see #resetRestrictions() - */ - public Tree getParsedTree() { - return tree; - } - - /** - * Outputs a summary of the current analysis to the Analyzer table using the - * default Tree label. - * - * @param groupByType if true measurements are grouped by SWC-type flag - * @see #run() - * @see #setTable(DefaultGenericTable) - */ - public void summarize(final boolean groupByType) { - summarize(tree.getLabel(), groupByType); - } - - /** - * Outputs a summary of the current analysis to the Analyzer table. - * - * @param rowHeader the String to be used as label for the summary - * @param groupByType if true measurements are grouped by SWC-type flag - * @see #run() - * @see #setTable(DefaultGenericTable) - */ - public void summarize(final String rowHeader, final boolean groupByType) { - measure(rowHeader, TreeStatistics.getMetrics("quick"), true); - } - - protected int getNextRow(final String rowHeader) { - table.appendRow((rowHeader==null)?"":rowHeader); - return table.getRowCount() - 1; - } - - /** - * Gets a list of supported metrics. Note that this list will only include - * commonly used metrics. For a complete list of supported metrics see - * {@link TreeStatistics#getAllMetrics()} )} - * - * @return the list of available metrics - * @see TreeStatistics#getMetrics(String) - */ - @SuppressWarnings("deprecation") - public static List getMetrics() { - final ArrayList metrics = new ArrayList<>(); - metrics.add(MultiTreeStatistics.ASSIGNED_VALUE); - metrics.add(MultiTreeStatistics.AVG_BRANCH_LENGTH); - metrics.add(MultiTreeStatistics.AVG_CONTRACTION); - metrics.add(MultiTreeStatistics.AVG_FRACTAL_DIMENSION); - metrics.add(MultiTreeStatistics.AVG_FRAGMENTATION); - metrics.add(MultiTreeStatistics.AVG_PARTITION_ASYMMETRY); - metrics.add(MultiTreeStatistics.AVG_REMOTE_ANGLE); - metrics.add(MultiTreeStatistics.HIGHEST_PATH_ORDER); - metrics.add(TreeStatistics.AVG_SPINE_DENSITY); - metrics.add(TreeStatistics.DEPTH); - metrics.add(TreeStatistics.BRANCH_EXTENSION_ANGLE_XY); - metrics.add(TreeStatistics.BRANCH_EXTENSION_ANGLE_XZ); - metrics.add(TreeStatistics.BRANCH_EXTENSION_ANGLE_ZY); - metrics.add(TreeStatistics.INNER_EXTENSION_ANGLE_XY); - metrics.add(TreeStatistics.INNER_EXTENSION_ANGLE_XZ); - metrics.add(TreeStatistics.INNER_EXTENSION_ANGLE_ZY); - metrics.add(TreeStatistics.PRIMARY_EXTENSION_ANGLE_XY); - metrics.add(TreeStatistics.PRIMARY_EXTENSION_ANGLE_XZ); - metrics.add(TreeStatistics.PRIMARY_EXTENSION_ANGLE_ZY); - metrics.add(TreeStatistics.TERMINAL_EXTENSION_ANGLE_XY); - metrics.add(TreeStatistics.TERMINAL_EXTENSION_ANGLE_XZ); - metrics.add(TreeStatistics.TERMINAL_EXTENSION_ANGLE_ZY); - metrics.add(TreeStatistics.HEIGHT); - metrics.add(TreeStatistics.INNER_LENGTH); - metrics.add(TreeStatistics.LENGTH); - metrics.add(TreeStatistics.N_BRANCH_POINTS); - metrics.add(TreeStatistics.N_BRANCHES); - metrics.add(TreeStatistics.N_FITTED_PATHS); - metrics.add(TreeStatistics.N_INNER_BRANCHES); - metrics.add(TreeStatistics.N_NODES); - metrics.add(TreeStatistics.N_PATHS); - metrics.add(TreeStatistics.N_PRIMARY_BRANCHES); - metrics.add(TreeStatistics.N_SPINES); - metrics.add(TreeStatistics.N_TERMINAL_BRANCHES); - metrics.add(TreeStatistics.N_TIPS); - metrics.add(TreeStatistics.PATH_MEAN_RADIUS); - metrics.add(TreeStatistics.PRIMARY_LENGTH); - metrics.add(TreeStatistics.STRAHLER_NUMBER); - metrics.add(TreeStatistics.STRAHLER_RATIO); - metrics.add(TreeStatistics.TERMINAL_LENGTH); - metrics.add(TreeStatistics.WIDTH); - metrics.add("Sholl: "+ ShollAnalyzer.SUM); - metrics.add("Sholl: "+ ShollAnalyzer.MAX); - metrics.add("Sholl: "+ ShollAnalyzer.N_MAX); - metrics.add("Sholl: "+ ShollAnalyzer.N_SECONDARY_MAX); - metrics.add("Sholl: "+ ShollAnalyzer.POLY_FIT_DEGREE); - return metrics; - } - - /** - * Computes the specified metric. - * - * @param metric the metric to be computed (case-insensitive). While it is - * expected to be an element of {@link #getMetrics()}, it can be - * specified in a "loose" manner: If {@code metric} is not - * initially recognized, a heuristic will match it to the closest - * entry in the list of possible metrics. E.g., "# bps", "n - * junctions", will be both mapped to - * {@link MultiTreeStatistics#N_BRANCH_POINTS}. Details on the - * matching are printed to the Console when in debug mode. - * @return the computed value - * @throws IllegalArgumentException if metric is not recognized - * @see #getMetrics() - */ - public Number getMetric(final String metric) throws IllegalArgumentException { - return getMetricInternal(TreeStatistics.getNormalizedMeasurement(metric)); - } - - protected Number getMetricInternal(final String metric) { - try { - return getMetricWithoutChecks(metric); - } catch (final Exception ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - return Double.NaN; - } - } - - @SuppressWarnings("deprecation") - protected Number getMetricWithoutChecks(final String metric) throws UnknownMetricException { - switch (metric) { - case MultiTreeStatistics.ASSIGNED_VALUE: - return tree.getAssignedValue(); - case MultiTreeStatistics.AVG_BRANCH_LENGTH: - return getAvgBranchLength(); - case MultiTreeStatistics.AVG_CONTRACTION: - return getAvgContraction(); - case MultiTreeStatistics.AVG_FRAGMENTATION: - return getAvgFragmentation(); - case MultiTreeStatistics.AVG_REMOTE_ANGLE: - return getAvgRemoteBifAngle(); - case MultiTreeStatistics.AVG_PARTITION_ASYMMETRY: - return getAvgPartitionAsymmetry(); - case MultiTreeStatistics.AVG_FRACTAL_DIMENSION: - return getAvgFractalDimension(); - case MultiTreeStatistics.PRIMARY_LENGTH: - case TreeStatistics.PRIMARY_LENGTH: - return getPrimaryLength(); - case MultiTreeStatistics.TERMINAL_LENGTH: - case TreeStatistics.TERMINAL_LENGTH: - return getTerminalLength(); - case MultiTreeStatistics.INNER_LENGTH: - case TreeStatistics.INNER_LENGTH: - return getInnerLength(); - case TreeStatistics.AVG_SPINE_DENSITY: - case TreeStatistics.PATH_SPINE_DENSITY: - return getSpineOrVaricosityDensity(); - case TreeStatistics.DEPTH: - return getDepth(); - case TreeStatistics.HEIGHT: - return getHeight(); - case MultiTreeStatistics.HIGHEST_PATH_ORDER: - return getHighestPathOrder(); - case TreeStatistics.LENGTH: - return getCableLength(); - case TreeStatistics.PATH_MEAN_RADIUS: - case MultiTreeStatistics.MEAN_RADIUS: - final TreeStatistics treeStats = new TreeStatistics(tree); - return treeStats.getSummaryStats(TreeStatistics.PATH_MEAN_RADIUS).getMean(); - case TreeStatistics.N_BRANCH_POINTS: - return getBranchPoints().size(); - case TreeStatistics.N_BRANCHES: - return getNBranches(); - case TreeStatistics.N_FITTED_PATHS: - return getNFittedPaths(); - case TreeStatistics.N_NODES: - return getNNodes(); - case TreeStatistics.N_PATHS: - return getNPaths(); - case TreeStatistics.N_PRIMARY_BRANCHES: - return getPrimaryBranches().size(); - case TreeStatistics.N_INNER_BRANCHES: - return getInnerBranches().size(); - case TreeStatistics.N_TERMINAL_BRANCHES: - return getTerminalBranches().size(); - case TreeStatistics.N_TIPS: - return getTips().size(); - case TreeStatistics.N_SPINES: - return getNoSpinesOrVaricosities(); - case TreeStatistics.STRAHLER_NUMBER: - return getStrahlerNumber(); - case TreeStatistics.STRAHLER_RATIO: - return getStrahlerBifurcationRatio(); - case TreeStatistics.WIDTH: - return getWidth(); - default: - if (metric.startsWith("Sholl: ")) { - return getShollMetric(metric); - } - throw new UnknownMetricException("Unrecognizable measurement \"" + metric + "\". " - + "Maybe you meant one of the following?: \"" + String.join(", ", getMetrics() + "\"")); - } - } - - protected Number getShollMetric(final String metric) { - final String fMetric = metric.substring(metric.indexOf("Sholl: ") + 6).trim(); - return getShollAnalyzer().getSingleValueMetrics().getOrDefault(fMetric, Double.NaN); - } - - /** - * Measures this Tree, outputting the result to this Analyzer table using - * default row labels. If a Context has been specified, the table is updated. - * Otherwise, table contents are printed to Console. - * - * @param metrics the list of metrics to be computed. When null or an empty - * collection is specified, {@link #getMetrics()} is used. - * @param groupByType if false, metrics are computed to all branches in the - * Tree. If true, measurements will be split by SWC type - * annotations (axon, dendrite, etc.) - */ - public void measure(final Collection metrics, final boolean groupByType) { - measure(tree.getLabel(), metrics, groupByType); - } - - /** - * Measures this Tree, outputting the result to this Analyzer table. If a - * Context has been specified, the table is updated. Otherwise, table contents - * are printed to Console. - * - * @param rowHeader the row header label - * @param metrics the list of metrics to be computed. When null or an empty - * collection is specified, {@link #getMetrics()} is used. - * @param groupByType if false, metrics are computed to all branches in the - * Tree. If true, measurements will be split by SWC type - * annotations (axon, dendrite, etc.) - */ - public void measure(final String rowHeader, final Collection metrics, final boolean groupByType) { - if (table == null) table = new SNTTable(); - final Collection measuringMetrics = (metrics == null || metrics.isEmpty()) ? getMetrics() : metrics; - if (groupByType) { - for (final int type : tree.getSWCTypes(false)) { - restrictToSWCType(type); - final int row = getNextRow(rowHeader); - table.set(getCol("SWC Type(s)"), row, Path.getSWCtypeName(type, true)); - measuringMetrics.forEach(metric -> table.set(getCol(metric), row, getMetricInternal(metric))); - resetRestrictions(); - } - } else { - final int row = getNextRow(rowHeader); - table.set(getCol("SWC Type(s)"), row, getSWCTypesAsString()); - measuringMetrics.forEach(metric -> table.set(getCol(metric), row, getMetricInternal(metric))); - } - updateAndDisplayTable(); - } - - protected String getSWCTypesAsString() { - final StringBuilder sb = new StringBuilder(); - final Set types = tree.getSWCTypes(true); - for (int type: types) { - sb.append(Path.getSWCtypeName(type, true)).append(" "); - } - return sb.toString().trim(); - } - - /** - * Sets the Analyzer table. - * - * @param table the table to be used by the analyzer - * @see #summarize(boolean) - */ - public void setTable(final DefaultGenericTable table) { - this.table = table; - } - - /** - * Sets the table. - * - * @param table the table to be used by the analyzer - * @param title the title of the table display window - */ - public void setTable(final DefaultGenericTable table, final String title) { - this.table = table; - this.tableTitle = title; - } - - /** - * Gets the table currently being used by the Analyzer - * - * @return the table - */ - public DefaultGenericTable getTable() { - return table; - } - - /** - * Generates detailed summaries in which measurements are grouped by SWC-type - * flags - * - * @see #summarize(String, boolean) - */ - @Override - public void run() { - if (tree.list() == null || tree.list().isEmpty()) { - cancel("No Paths to Measure"); - return; - } - if (statusService !=null) - statusService.showStatus("Measuring Paths..."); - summarize(true); - if (statusService !=null) - statusService.clearStatus(); - } - - /** - * Updates and displays the Analyzer table. - */ - public void updateAndDisplayTable() { - if (getContext() == null) { - System.out.println(SNTTable.toString(table, 0, table.getRowCount() - 1)); - return; - } - final String displayName = (tableTitle == null) ? "SNT Measurements" - : tableTitle; - final Display display = displayService.getDisplay(displayName); - if (display != null) { - display.update(); - } - else { - displayService.createDisplay(displayName, table); - } - } - - public String getUnit(final String metric) { - final String m = metric.toLowerCase(); - if (TreeStatistics.WIDTH.equals(metric) || TreeStatistics.HEIGHT.equals(metric) - || TreeStatistics.DEPTH.equals(metric) || m.contains("length") || m.contains("radius") - || m.contains("distance") || TreeStatistics.CONVEX_HULL_ELONGATION.equals(metric)) { - return getUnit(); - } else if (m.contains("volume")) { - return getUnit() + "³"; - } else if (m.contains("surface area")) { - return getUnit() + "²"; - } else if (TreeStatistics.CONVEX_HULL_SIZE.equals(metric)) { - return getUnit() + ((tree.is3D()) ? "³" : "²"); - } else if (TreeStatistics.CONVEX_HULL_BOUNDARY_SIZE.equals(metric)) { - return getUnit() + ((tree.is3D()) ? "²" : ""); - } - return ""; - } - - protected String getUnit() { - return (String) tree.getProperties().getOrDefault(TreeProperties.KEY_SPATIAL_UNIT, "? units"); - } - - protected int getCol(final String header) { - final String normHeader = AnalysisUtils.getMetricLabel(header, tree); - int idx = table.getColumnIndex(normHeader); - if (idx == -1) { - table.appendColumn(normHeader); - idx = table.getColumnCount() - 1; - } - return idx; - } - - protected int getSinglePointPaths() { - return (int) tree.list().stream().filter(p -> p.size() == 1).count(); - } - - /** - * Gets the no. of paths parsed by the Analyzer. - * - * @return the number of paths - */ - public int getNPaths() { - return tree.list().size(); - } - - protected int getNFittedPaths() { - return fittedPathsCounter; - } - - public double getWidth() { - return tree.getBoundingBox(true).width(); - } - - public double getHeight() { - return tree.getBoundingBox(true).height(); - } - - public double getDepth() { - return tree.getBoundingBox(true).depth(); - } - - /** - * Retrieves all the Paths in the analyzed Tree tagged as primary. - * - * @return the list of primary paths. - * @see #getPrimaryBranches() - */ - public List getPrimaryPaths() { - final List primaryPaths = new ArrayList<>(); - for (final Path p : tree.list()) { - if (p.isPrimary()) primaryPaths.add(p); - } - return primaryPaths; - } - - /** - * Retrieves the branches of highest - * {@link StrahlerAnalyzer#getHighestBranchOrder() Strahler order} in the Tree. - * This typically correspond to the most 'internal' branches of the Tree in - * direct sequence from the root. - * - * @return the list containing the "inner" branches. Note that these branches - * (Path segments) will not carry any connectivity information. - * @see #getPrimaryPaths() - * @see StrahlerAnalyzer#getBranches() - * @see StrahlerAnalyzer#getHighestBranchOrder() - */ - public List getInnerBranches() { - getStrahlerAnalyzer(); - innerBranches = sAnalyzer.getBranches(sAnalyzer.getHighestBranchOrder()); - return innerBranches; - } - - /** - * Retrieves the primary branches of the analyzed Tree. Primary branches (or - * root-associated) have origin in the Tree's root, extending to the closest - * branch/end-point. Note that a primary branch can also be terminal. - * - * @return the primary branches. Note that these branches (Path segments) will - * not carry any connectivity information. - * @see #getPrimaryPaths() - * @see StrahlerAnalyzer#getRootAssociatedBranches() - */ - public List getPrimaryBranches() { - getStrahlerAnalyzer(); - primaryBranches = sAnalyzer.getRootAssociatedBranches(); - return primaryBranches; - } - - /** - * Retrieves the terminal branches of the analyzed Tree. A terminal branch - * corresponds to the section of a terminal Path between its last branch-point - * and its terminal point (tip). A terminal branch can also be primary. - * - * @return the terminal branches. Note that as per - * {@link Path#getSection(int, int)}, these branches will not carry any - * connectivity information. - * @see #getPrimaryBranches - * @see #restrictToOrder(int...) - */ - public List getTerminalBranches() { - getStrahlerAnalyzer(); - terminalBranches = sAnalyzer.getBranches(1); - return terminalBranches; - } - - /** - * Gets the position of all the tips in the analyzed tree. - * - * @return the set of terminal points - */ - public Set getTips() { - - // retrieve all start/end points - tips = new HashSet<>(); - for (final Path p : tree.list()) { - tips.add(p.getNode(p.size() - 1)); - } - // now remove any joint-associated point - if (joints == null) getBranchPoints(); - tips.removeAll(joints); - return tips; - - } - - /** - * Gets the position of all the tips in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried (all of its children will be - * considered). Null not allowed. - * @return the branch points positions, or an empty set if no tips were - * retrieved. - */ - public Set getTips(final BrainAnnotation annot) { - return getTips(annot, true); - } - - /** - * Gets the position of all the tips in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. Null not allowed. - * @param includeChildren whether children of {@code annot} should be included. - * @return the branch points positions, or an empty set if no tips were - * retrieved. - */ - public Set getTips(final BrainAnnotation annot, final boolean includeChildren) { - if (tips == null) - getTips(); - final HashSet fTips = new HashSet<>(); - for (final PointInImage tip : tips) { - final BrainAnnotation annotation = tip.getAnnotation(); - if (annotation != null && contains(annot, annotation, includeChildren)) - fTips.add(tip); - } - return fTips; - } - - /** - * Gets the number of end points in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. All of its children will be - * considered - * @return the number of end points - */ - public int getNtips(final BrainAnnotation annot) { - return getNtips(annot, true); - } - - /** - * Gets the number of end points in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. - * @param includeChildren whether children of {@code annot} should be included. - * @return the number of end points - */ - public int getNtips(final BrainAnnotation annot, final boolean includeChildren) { - return getTips(annot, true).size(); - } - - /** - * Gets the number of end points in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. All of its children will be - * considered - * @return the number of end points - */ - public double getNtipsNorm(final BrainAnnotation annot) { - return getNtipsNorm(annot, true); - } - - /** - * Gets the percentage of end points in the analyzed tree associated with the - * specified annotation - * - * @param annot the BrainAnnotation to be queried. - * @param includeChildren whether children of {@code annot} should be included - * @return the ratio between the no. of branch points associated with - * {@code annot} and the total number of end points in the tree. - */ - public double getNtipsNorm(final BrainAnnotation annot, final boolean includeChildren) { - return (double) (getNtips(annot, includeChildren)) / (double)(tips.size()); - } - - /** - * Gets the position of all the branch points in the analyzed tree. - * - * @return the branch points positions - */ - public Set getBranchPoints() { - joints = new HashSet<>(); - for (final Path p : tree.list()) { - joints.addAll(p.getJunctionNodes()); - } - return joints; - } - - /** - * Gets the position of all the branch points in the analyzed tree associated - * with the specified annotation. - * - * @param annot the BrainAnnotation to be queried. All of its children are - * considered. - * @return the branch points positions, or an empty set if no branch points were - * retrieved. - */ - public Set getBranchPoints(final BrainAnnotation annot) { - return getBranchPoints(annot, true); - } - - /** - * Gets the position of all the branch points in the analyzed tree associated - * with the specified annotation. - * - * @param annot the BrainAnnotation to be queried. - * @param includeChildren whether children of {@code annot} should be included - * @return the branch points positions, or an empty set if no branch points were - * retrieved. - */ - public Set getBranchPoints(final BrainAnnotation annot, final boolean includeChildren) { - if (joints == null) getBranchPoints(); - final HashSet fJoints = new HashSet<>(); - for (final PointInImage joint : joints) { - final BrainAnnotation annotation = joint.getAnnotation(); - if (annotation != null && contains(annot, annotation, includeChildren)) - fJoints.add(joint); - } - return fJoints; - } - - /** - * Gets the number of branch points in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. All of its children are - * considered. - * @return the number of branch points - */ - public int getNbranchPoints(final BrainAnnotation annot) { - return getNbranchPoints(annot, true); - } - - /** - * Gets the number of branch points in the analyzed tree associated with the - * specified annotation. - * - * @param annot the BrainAnnotation to be queried. - * @param includeChildren whether children of {@code annot} should be included - * @return the number of branch points - */ - public int getNbranchPoints(final BrainAnnotation annot, final boolean includeChildren) { - return getBranchPoints(annot, includeChildren).size(); - } - - /** - * Gets the percentage of branch points in the analyzed tree associated with the - * specified annotation - * - * @param annot the BrainAnnotation to be queried. All of its children are - * considered. - * @return the ratio between the no. of branch points associated with - * {@code annot} and the total number of branch points in the tree. - */ - public double getNbranchPointsNorm(final BrainAnnotation annot) { - return getNbranchPointsNorm(annot, true); - } - - /** - * Gets the percentage of branch points in the analyzed tree associated with the - * specified annotation - * - * @param annot the BrainAnnotation to be queried. - * @param includeChildren whether children of {@code annot} should be included - * @return the ratio between the no. of branch points associated with - * {@code annot} and the total number of branch points in the tree. - */ - public double getNbranchPointsNorm(final BrainAnnotation annot, final boolean includeChildren) { - return (double) (getNbranchPoints(annot, includeChildren)) / (double) (joints.size()); - } - - /** - * Gets the cable length. - * - * @return the cable length of the tree - */ - public double getCableLength() { - return sumLength(tree.list()); - } - - /** - * Gets the cable length associated with the specified compartment (neuropil - * label). - * - * @param compartment the query compartment (null not allowed). All of its - * children will be considered - * @return the filtered cable length - */ - public double getCableLength(final BrainAnnotation compartment) { - return getCableLength(compartment, true); - } - - /** - * Gets the cable length associated with the specified compartment (neuropil - * label) as a ratio of total length. - * - * @param compartment the query compartment (null not allowed). All of its - * children will be considered - * @return the filtered cable length normalized to total cable length - */ - public double getCableLengthNorm(final BrainAnnotation compartment) { - return getCableLength(compartment, true) / getCableLength(); - } - - /** - * Gets the cable length associated with the specified compartment (neuropil - * label) as a ratio of total length. - * - * @param compartment the query compartment (null not allowed) - * @param includeChildren whether children of {@code compartment} should be - * included - * @return the filtered cable length normalized to total cable length - */ - public double getCableLengthNorm(final BrainAnnotation compartment, final boolean includeChildren) { - return getCableLength(compartment, includeChildren) / getCableLength(); - } - - /** - * Gets the cable length associated with the specified compartment (neuropil - * label). - * - * @param compartment the query compartment (null not allowed) - * @param includeChildren whether children of {@code compartment} should be included - * @return the filtered cable length - */ - public double getCableLength(final BrainAnnotation compartment, final boolean includeChildren) { - final DirectedWeightedGraph graph = tree.getGraph(); - final NodeStatistics nodeStats = new NodeStatistics<>(graph.vertexSet()); - final DirectedWeightedSubgraph subgraph = graph.getSubgraph(new HashSet<>(nodeStats.get(compartment, includeChildren))); - return subgraph.sumEdgeWeights(true); - } - - protected boolean isSameOrParentAnnotation(final BrainAnnotation annot, final BrainAnnotation annotToBeTested) { - return contains(annot, annotToBeTested, true); - } - - protected boolean contains(final BrainAnnotation annot, final BrainAnnotation annotToBeTested, - final boolean includeChildren) { - if (includeChildren) - return annot.equals(annotToBeTested) || annot.isParentOf(annotToBeTested); - return annot.equals(annotToBeTested); - } - - public Set getAnnotations() { - final HashSet set = new HashSet<>(); - for (final Path path : tree.list()) { - for (int i = 0; i < path.size(); i++) { - final BrainAnnotation annotation = path.getNodeAnnotation(i); - if (annotation != null) set.add(annotation); - } - } - return set; - } - - public Set getAnnotations(final int level) { - final Set filteredAnnotations = new HashSet<>(); - getAnnotations().forEach(annot -> { - final int depth = annot.getOntologyDepth(); - if (depth > level) { - filteredAnnotations.add(annot.getAncestor(level - depth)); - } else { - filteredAnnotations.add(annot); - } - }); - return filteredAnnotations; - } - - /** - * Gets the cable length of primary branches. - * - * @return the length sum of all primary branches - * @see #getPrimaryBranches() - */ - public double getPrimaryLength() { - if (primaryBranches == null) getPrimaryBranches(); - return sumLength(primaryBranches); - } - - /** - * Gets the cable length of inner branches - * - * @return the length sum of all inner branches - * @see #getInnerBranches() - */ - public double getInnerLength() { - if (innerBranches == null) getInnerBranches(); - return sumLength(innerBranches); - } - - /** - * Gets the cable length of terminal branches - * - * @return the length sum of all terminal branches - * @see #getTerminalBranches() - */ - public double getTerminalLength() { - if (terminalBranches == null) getTerminalBranches(); - return sumLength(terminalBranches); - } - - /** - * Gets the highest {@link sc.fiji.snt.Path#getOrder() path order} of the analyzed tree - * - * @return the highest Path order, or -1 if Paths in the Tree have no defined - * order - * @see #getStrahlerNumber() - */ - public int getHighestPathOrder() { - int root = -1; - for (final Path p : tree.list()) { - final int order = p.getOrder(); - if (order > root) root = order; - } - return root; - } - - /** - * Checks whether this tree is topologically valid, i.e., contains only one root - * and no loops. - * - * @return true, if Tree is valid, false otherwise - */ - public boolean isValid() { - if (sAnalyzer == null) - sAnalyzer = new StrahlerAnalyzer(tree); - try { - sAnalyzer.getGraph(); - return true; - } catch (final IllegalArgumentException ignored) { - return false; - } - } - - /** - * Gets the highest {@link StrahlerAnalyzer#getRootNumber() Strahler number} of - * the analyzed tree. - * - * @return the highest Strahler (root) number order - * @throws IllegalArgumentException if tree contains multiple roots or loops - */ - public int getStrahlerNumber() throws IllegalArgumentException { - getStrahlerAnalyzer(); - return sAnalyzer.getRootNumber(); - } - - /** - * Gets the {@link StrahlerAnalyzer} instance associated with this analyzer - * - * @return the StrahlerAnalyzer instance associated with this analyzer - * @throws IllegalArgumentException if tree contains multiple roots or loops - */ - public StrahlerAnalyzer getStrahlerAnalyzer() throws IllegalArgumentException { - if (sAnalyzer == null) sAnalyzer = new StrahlerAnalyzer(tree); - return sAnalyzer; - } - - /** - * Gets the {@link ShollAnalyzer} instance associated with this analyzer. Note - * that changes to {@link ShollAnalyzer} must be performed before calling - * {@link #getMetric(String)}, {@link #measure(Collection, boolean)}, etc. - * - * @return the ShollAnalyzer instance associated with this analyzer - */ - public ShollAnalyzer getShollAnalyzer() { - if (shllAnalyzer == null) shllAnalyzer = new ShollAnalyzer(tree, this); - return shllAnalyzer; - } - - /** - * Gets the average {@link StrahlerAnalyzer#getAvgBifurcationRatio() Strahler - * bifurcation ratio} of the analyzed tree. - * - * @return the average bifurcation ratio - * @throws IllegalArgumentException if tree contains multiple roots or loops - */ - public double getStrahlerBifurcationRatio() throws IllegalArgumentException { - getStrahlerAnalyzer(); - return sAnalyzer.getAvgBifurcationRatio(); - } - - /** - * Gets the number of branches in the analyzed tree. - * - * @return the number of branches - * @throws IllegalArgumentException if tree contains multiple roots or loops - */ - public int getNBranches() throws IllegalArgumentException { - return getBranches().size(); - } - - /** - * Gets the number of nodes in the analyzed tree. - * - * @return the number of nodes - */ - public long getNNodes() { - return tree.getNodesCount(); - } - - /** - * Gets all the branches in the analyzed tree. A branch is defined as the Path - * composed of all the nodes between two branching points or between one - * branching point and a termination point. - * - * @return the list of branches as Path objects. - * @see StrahlerAnalyzer#getBranches() - * @throws IllegalArgumentException if tree contains multiple roots or loops - */ - public List getBranches() throws IllegalArgumentException { - getStrahlerAnalyzer(); - return sAnalyzer.getBranches().values().stream().flatMap(List::stream).collect(Collectors.toList()); - } - - /** - * Gets average {@link Path#getContraction() contraction} for all the branches - * of the analyzed tree. - * - * @throws IllegalArgumentException if tree contains multiple roots or loops - * @return the average branch contraction - */ - public double getAvgContraction() throws IllegalArgumentException { - double contraction = 0; - final List branches = getBranches(); - for (final Path p : branches) { - final double pContraction = p.getContraction(); - if (!Double.isNaN(pContraction)) contraction += pContraction; - } - return contraction / branches.size(); - } - - public double getAvgFragmentation() { - double fragmentation = 0; - final List branches = getBranches(); - for (final Path p : branches) { - fragmentation += p.size(); - } - return fragmentation / branches.size(); - } - - /** - * Gets average length for all the branches of the analyzed tree. - * - * @throws IllegalArgumentException if tree contains multiple roots or loops - * @return the average branch length - */ - public double getAvgBranchLength() throws IllegalArgumentException { - final List branches = getBranches(); - return sumLength(getBranches()) / branches.size(); - } - - /** - * Gets the angle between each bifurcation point and its children in the simplified graph, - * which comprise either branch points or terminal nodes. - * Note that branch points with more than 2 children are ignored. - * - * @return the list of remote bifurcation angles - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public List getRemoteBifAngles() throws IllegalArgumentException { - final DirectedWeightedGraph sGraph = tree.getGraph(true); - final List branchPoints = sGraph.getBPs(); - final List angles = new ArrayList<>(); - for (final SWCPoint bp : branchPoints) { - final List children = Graphs.successorListOf(sGraph, bp); - // Only consider bifurcations - if (children.size() > 2) { - continue; - } - final SWCPoint c0 = children.get(0); - final SWCPoint c1 = children.get(1); - // Get vector for each parent-child link - final double[] v0 = new double[] { c0.getX() - bp.getX(), c0.getY() - bp.getY(), c0.getZ() - bp.getZ() }; - final double[] v1 = new double[] { c1.getX() - bp.getX(), c1.getY() - bp.getY(), c1.getZ() - bp.getZ() }; - // Dot product - double dot = 0.0; - for (int i = 0 ; i < v0.length ; i++) { - dot += v0[i]*v1[i]; - } - final double cosineAngle = (double) dot / ( Math.sqrt(v0[0]*v0[0] + v0[1]*v0[1] + v0[2]*v0[2]) * Math.sqrt(v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2]) ); - final double angleRadians = Math.acos(cosineAngle); - final double angleDegrees = angleRadians * ( (double) 180.0 / Math.PI ); - angles.add(angleDegrees); - } - return angles; - } - - /** - * Gets the average remote bifurcation angle of the analyzed tree. - * Note that branch points with more than 2 children are ignored during the computation. - * - * @return the average remote bifurcation angle - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public double getAvgRemoteBifAngle() throws IllegalArgumentException { - final List angles = getRemoteBifAngles(); - double sumAngles = 0.0; - for (final double a : angles) { - sumAngles += a; - } - return (double) sumAngles / angles.size(); - } - - /** - * Gets the partition asymmetry at each bifurcation point in the analyzed tree. - * Note that branch points with more than 2 children are ignored. - * - * @return a list containing the partition asymmetry at each bifurcation point - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public List getPartitionAsymmetry() throws IllegalArgumentException { - final DirectedWeightedGraph sGraph = tree.getGraph(true); - final List branchPoints = sGraph.getBPs(); - final List resultList = new ArrayList<>(); - for (final SWCPoint bp : branchPoints) { - final List children = Graphs.successorListOf(sGraph, bp); - // Only consider bifurcations - if (children.size() > 2) { - continue; - } - final List tipCounts = new ArrayList<>(); - for (final SWCPoint child : children) { - int count = 0; - final DepthFirstIterator dfi = sGraph.getDepthFirstIterator(child); - while (dfi.hasNext()) { - final SWCPoint node = dfi.next(); - if (Graphs.successorListOf(sGraph, node).size() == 0) { - count++; - } - } - tipCounts.add(count); - } - double asymmetry; - // Make sure we avoid getting NaN - if (tipCounts.get(0).equals(tipCounts.get(1))) { - asymmetry = 0.0; - } - else { - asymmetry = (double) Math.abs(tipCounts.get(0) - tipCounts.get(1)) / (tipCounts.get(0) + tipCounts.get(1) - 2); - } - resultList.add(asymmetry); - } - return resultList; - } - - /** - * Gets the average partition asymmetry of the analyzed tree. - * Note that branch points with more than 2 children are ignored during the computation. - * - * @return the average partition asymmetry - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public double getAvgPartitionAsymmetry() throws IllegalArgumentException { - final List asymmetries = getPartitionAsymmetry(); - double sumAsymmetries = 0.0; - for (final double a : asymmetries) { - sumAsymmetries += a; - } - return (double) sumAsymmetries / asymmetries.size(); - } - - /** - * Gets the fractal dimension of each branch in the analyzed tree. - * Note that branches with less than 5 points are ignored. - * - * @return a list containing the fractal dimension of each branch - * @see StrahlerAnalyzer#getBranches() - * @see Path#getFractalDimension() - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public List getFractalDimension() throws IllegalArgumentException { - final List fractalDims = new ArrayList<>(); - for (final Path b : getBranches()) { - final double fDim = b.getFractalDimension(); - if (!Double.isNaN(fDim)) fractalDims.add(fDim); - } - return fractalDims; - } - - /** - * Gets the average fractal dimension of the analyzed tree. - * Note that branches with less than 5 points are ignored during the computation. - * - * @return the average fractal dimension - * @throws IllegalArgumentException if the tree contains multiple roots or loops - */ - public double getAvgFractalDimension() throws IllegalArgumentException { - final List fractalDims = getFractalDimension(); - double sumDims = 0.0; - for (final double fDim : fractalDims) { - sumDims += fDim; - } - return (double) sumDims / fractalDims.size(); - - } - - /** - * Gets the number of spines/varicosities that have been (manually) assigned to - * tree being analyzed. - * - * @return the number of spines/varicosities - */ - public int getNoSpinesOrVaricosities() { - return tree.list().stream().mapToInt(p -> p.getSpineOrVaricosityCount()).sum(); - } - /** - * Gets the overall density of spines/varicosities associated with this tree - * - * @return the spine/varicosity density (same as - * {@code getNoSpinesOrVaricosities()/getCableLength()}) - */ - public double getSpineOrVaricosityDensity() { - return getNoSpinesOrVaricosities() / getCableLength(); - } +/** @deprecated Use {@link TreeStatistics} instead */ +@Deprecated +public class TreeAnalyzer extends TreeStatistics { - private double sumLength(final Collection paths) { - double totalLength = 0d; - for (final Path p : paths) { - if (p.getStartJoins() != null) { - totalLength += p.getStartJoinsPoint().distanceTo(p.getNode(0)); - } - totalLength += p.getLength(); - } - return totalLength; - } + public TreeAnalyzer(final Tree tree) { + super(tree); + } - /* IDE debug method */ - public static void main(final String[] args) throws InterruptedException { - final ImageJ ij = new ImageJ(); - final SNTService sntService = ij.context().getService(SNTService.class); - final Tree tree = sntService.demoTrees().get(0); - final TreeAnalyzer analyzer = new TreeAnalyzer(tree); - TreeAnalyzer.getMetrics().forEach( m -> { - System.out.println(m + ": " + analyzer.getMetric(m)); - }); - } } diff --git a/src/main/java/sc/fiji/snt/analysis/TreeColorMapper.java b/src/main/java/sc/fiji/snt/analysis/TreeColorMapper.java index 9d4b7780..1535a0fc 100644 --- a/src/main/java/sc/fiji/snt/analysis/TreeColorMapper.java +++ b/src/main/java/sc/fiji/snt/analysis/TreeColorMapper.java @@ -66,7 +66,7 @@ */ public class TreeColorMapper extends ColorMapper { - /* For convenience keep references to TreeAnalyzer fields */ + /* For convenience keep references to TreeStatistics fields */ /** Mapping property: Internode angle */ public static final String INTER_NODE_ANGLE = MultiTreeStatistics.INTER_NODE_ANGLE; /** Mapping property: Internode distance */ diff --git a/src/main/java/sc/fiji/snt/analysis/TreeStatistics.java b/src/main/java/sc/fiji/snt/analysis/TreeStatistics.java index 1f9ee707..5ad3032d 100644 --- a/src/main/java/sc/fiji/snt/analysis/TreeStatistics.java +++ b/src/main/java/sc/fiji/snt/analysis/TreeStatistics.java @@ -22,1519 +22,2642 @@ package sc.fiji.snt.analysis; -import java.awt.Dimension; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.jfree.chart.JFreeChart; import org.jfree.data.category.DefaultCategoryDataset; - +import org.jgrapht.Graphs; +import org.jgrapht.traverse.DepthFirstIterator; +import org.scijava.app.StatusService; +import org.scijava.command.ContextCommand; +import org.scijava.display.Display; +import org.scijava.display.DisplayService; +import org.scijava.plugin.Parameter; +import org.scijava.table.DefaultGenericTable; import sc.fiji.snt.Path; import sc.fiji.snt.SNTUtils; import sc.fiji.snt.Tree; +import sc.fiji.snt.TreeProperties; import sc.fiji.snt.analysis.AnalysisUtils.HistogramDatasetPlus; import sc.fiji.snt.analysis.graph.DirectedWeightedGraph; import sc.fiji.snt.analysis.graph.DirectedWeightedSubgraph; +import sc.fiji.snt.analysis.graph.SWCWeightedEdge; import sc.fiji.snt.annotation.AllenCompartment; import sc.fiji.snt.annotation.BrainAnnotation; import sc.fiji.snt.io.MouseLightLoader; import sc.fiji.snt.util.PointInImage; import sc.fiji.snt.util.SWCPoint; +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.stream.Collectors; + /** - * Computes summary and descriptive statistics from univariate properties of - * Paths and Nodes in a {@link Tree}. For analysis of groups of Trees have a - * look at {@link MultiTreeStatistics} and {@link GroupedTreeStatistics}. + * Computes summary and descriptive statistics from properties of Paths and Nodes in a {@link Tree}, including + * convenience methods to plot distributions of such data. For analysis of groups of Trees have a look at + * {@link MultiTreeStatistics} and {@link GroupedTreeStatistics}. * * @author Tiago Ferreira * @author Cameron Arshadi */ -public class TreeStatistics extends TreeAnalyzer { - - // branch angles - /** Metric: Branch extension angle XY */ - public static final String BRANCH_EXTENSION_ANGLE_XY = "Branch extension angle XY"; - /** Metric: Branch extension angle XZ */ - public static final String BRANCH_EXTENSION_ANGLE_XZ = "Branch extension angle XZ"; - /** Metric: Branch extension angle ZY */ - public static final String BRANCH_EXTENSION_ANGLE_ZY = "Branch extension angle ZY"; - /** Flag for {@value #INNER_EXTENSION_ANGLE_XY} analysis. */ - public static final String INNER_EXTENSION_ANGLE_XY = "Inner branches: Extension angle XY"; - /** Flag for {@value #INNER_EXTENSION_ANGLE_XZ} analysis. */ - public static final String INNER_EXTENSION_ANGLE_XZ = "Inner branches: Extension angle XZ"; - /** Flag for {@value #INNER_EXTENSION_ANGLE_ZY} analysis. */ - public static final String INNER_EXTENSION_ANGLE_ZY = "Inner branches: Extension angle ZY"; - /** Flag for {@value #PRIMARY_EXTENSION_ANGLE_XY} analysis. */ - public static final String PRIMARY_EXTENSION_ANGLE_XY = "Primary branches: Extension angle XY"; - /** Flag for {@value #PRIMARY_EXTENSION_ANGLE_XZ} analysis. */ - public static final String PRIMARY_EXTENSION_ANGLE_XZ = "Primary branches: Extension angle XZ"; - /** Flag for {@value #PRIMARY_EXTENSION_ANGLE_ZY} analysis. */ - public static final String PRIMARY_EXTENSION_ANGLE_ZY = "Primary branches: Extension angle ZY"; - /** Flag for {@value #TERMINAL_EXTENSION_ANGLE_XY} analysis. */ - public static final String TERMINAL_EXTENSION_ANGLE_XY = "Terminal branches: Extension angle XY"; - /** Flag for {@value #TERMINAL_EXTENSION_ANGLE_XZ} analysis. */ - public static final String TERMINAL_EXTENSION_ANGLE_XZ = "Terminal branches: Extension angle XZ"; - /** Flag for {@value #TERMINAL_EXTENSION_ANGLE_ZY} analysis. */ - public static final String TERMINAL_EXTENSION_ANGLE_ZY = "Terminal branches: Extension angle ZY"; - /** Flag for {@value #REMOTE_BIF_ANGLES} statistics. */ - public static final String REMOTE_BIF_ANGLES = "Remote bif. angles"; - // paths - /** Flag for {@value #PATH_LENGTH} analysis. */ - public static final String PATH_LENGTH = "Path length"; - /** Flag for {@value #PATH_EXT_ANGLE_XY} analysis. */ - public static final String PATH_EXT_ANGLE_XY = "Path extension angle XY"; - /** Flag for {@value #PATH_EXT_ANGLE_XZ} analysis. */ - public static final String PATH_EXT_ANGLE_XZ = "Path extension angle XZ"; - /** Flag for {@value #PATH_EXT_ANGLE_ZY} analysis. */ - public static final String PATH_EXT_ANGLE_ZY = "Path extension angle ZY"; - /** Flag for {@value #PATH_EXT_ANGLE_REL_XY} analysis. */ - public static final String PATH_EXT_ANGLE_REL_XY = "Path extension angle XY (Rel.)"; - /** Flag for {@value #PATH_EXT_ANGLE_REL_XZ} analysis. */ - public static final String PATH_EXT_ANGLE_REL_XZ = "Path extension angle XZ (Rel.)"; - /** Flag for {@value #PATH_EXT_ANGLE_REL_ZY} analysis. */ - public static final String PATH_EXT_ANGLE_REL_ZY = "Path extension angle ZY (Rel.)"; - /** Flag for {@value #PATH_ORDER} statistics. */ - public static final String PATH_ORDER = "Path order"; - /** Flag for {@value #PATH_CHANNEL} statistics. */ - public static final String PATH_CHANNEL = "Path channel"; - /** Flag for {@value #PATH_FRAME} statistics. */ - public static final String PATH_FRAME = "Path frame"; - /** Flag for {@value #PATH_MEAN_RADIUS} statistics. */ - public static final String PATH_MEAN_RADIUS = "Path mean radius"; - /** Flag for {@value #PATH_SPINE_DENSITY} statistics */ - public static final String PATH_SPINE_DENSITY = "Path spine/varicosity density"; - /** Flag for {@value #PATH_CONTRACTION} statistics. */ - public static final String PATH_CONTRACTION = "Path contraction"; - /** Flag for {@value #PATH_FRACTAL_DIMENSION} statistics. */ - public static final String PATH_FRACTAL_DIMENSION = "Path fractal dimension"; - // branches - /** Flag for {@value #BRANCH_LENGTH} analysis. */ - public static final String BRANCH_LENGTH = "Branch length"; - /** Flag for {@value #BRANCH_MEAN_RADIUS} analysis. */ - public static final String BRANCH_MEAN_RADIUS = "Branch mean radius"; - /** Flag for {@value #TERMINAL_LENGTH} analysis. */ - public static final String TERMINAL_LENGTH = "Terminal branches: Length"; - /** Flag for {@value #PRIMARY_LENGTH} analysis. */ - public static final String PRIMARY_LENGTH = "Primary branches: Length"; - /** Flag for {@value #INNER_LENGTH} analysis. */ - public static final String INNER_LENGTH = "Inner branches: Length"; - /** Flag for {@value #BRANCH_CONTRACTION} statistics. */ - public static final String BRANCH_CONTRACTION = "Branch contraction"; - /** Flag for {@value #BRANCH_FRACTAL_DIMENSION} statistics. */ - public static final String BRANCH_FRACTAL_DIMENSION = "Branch fractal dimension"; - // nodes - /** Flag for {@value #NODE_RADIUS} statistics. */ - public static final String NODE_RADIUS = "Node radius"; - /** Flag for {@value #INTER_NODE_ANGLE} statistics. */ - public static final String INTER_NODE_ANGLE = "Internode angle"; - /** Flag for {@value #INTER_NODE_DISTANCE} statistics. */ - public static final String INTER_NODE_DISTANCE = "Internode distance"; - /** Flag for {@value #INTER_NODE_DISTANCE_SQUARED} statistics. */ - public static final String INTER_NODE_DISTANCE_SQUARED = "Internode distance (squared)"; - // counts - /** Flag for {@value #N_BRANCH_POINTS} statistics. */ - public static final String N_BRANCH_POINTS = "No. of branch points"; - /** Flag for {@value #N_NODES} statistics. */ - public static final String N_NODES = "No. of nodes"; - /** Flag for {@value #N_PATH_NODES} statistics. */ - public static final String N_PATH_NODES = "No. of path nodes (path fragmentation)"; - /** Flag for {@value #N_BRANCH_NODES} statistics. */ - public static final String N_BRANCH_NODES = "No. of branch nodes (branch fragmentation)"; - /** Flag for {@value #N_PATHS} statistics */ - public static final String N_PATHS = "No. of paths"; - /** Flag for {@value #N_SPINES} statistics. */ - public static final String N_SPINES = "No. of spines/varicosities"; - /** Flag specifying {@value #N_BRANCHES} statistics */ - public static final String N_BRANCHES = "No. of branches"; - /** Flag specifying {@value #N_PRIMARY_BRANCHES} statistics */ - public static final String N_PRIMARY_BRANCHES = "No. of primary branches"; - /** Flag specifying {@value #N_INNER_BRANCHES} statistics */ - public static final String N_INNER_BRANCHES = "No. of inner branches"; - /** Flag specifying {@value #N_TERMINAL_BRANCHES} statistics */ - public static final String N_TERMINAL_BRANCHES = "No. of terminal branches"; - /** Flag specifying {@value #N_TIPS} statistics */ - public static final String N_TIPS = "No. of tips"; - /** Flag for {@value #N_FITTED_PATHS} statistics */ - public static final String N_FITTED_PATHS = "No. of fitted paths"; - /** Flag for {@value #PATH_N_SPINES} statistics */ - public static final String PATH_N_SPINES = "No. of spines/varicosities per path"; - //misc - /** Flag for {@value #LENGTH} analysis. */ - public static final String LENGTH = "Cable length"; - /** Flag for {@value #COMPLEXITY_INDEX_ACI} statistics. */ - public static final String COMPLEXITY_INDEX_ACI = "Complexity index: ACI"; - /** Flag for {@value #COMPLEXITY_INDEX_DCI} statistics. */ - public static final String COMPLEXITY_INDEX_DCI = "Complexity index: DCI"; - /** Flag for {@value #X_COORDINATES} statistics. */ - public static final String X_COORDINATES = "X coordinates"; - /** Flag for {@value #Y_COORDINATES} statistics. */ - public static final String Y_COORDINATES = "Y coordinates"; - /** Flag for {@value #Z_COORDINATES} statistics. */ - public static final String Z_COORDINATES = "Z coordinates"; - /** Flag for {@value #WIDTH} statistics */ - public static final String WIDTH = "Width"; - /** Flag for {@value #HEIGHT} statistics */ - public static final String HEIGHT = "Height"; - /** Flag for {@value #DEPTH} statistics */ - public static final String DEPTH = "Depth"; - /** Flag for {@value #PARTITION_ASYMMETRY} statistics. */ - public static final String PARTITION_ASYMMETRY = "Partition asymmetry"; - // graph geodesics - /** Flag for {@value #GRAPH_DIAMETER} statistics. */ - public static final String GRAPH_DIAMETER = "Longest shortest path: Length"; - /** Flag for {@value #GRAPH_DIAMETER_ANGLE_XY} statistics. */ - public static final String GRAPH_DIAMETER_ANGLE_XY = "Longest shortest path: Extension angle XY"; - /** Flag for {@value #GRAPH_DIAMETER_ANGLE_XZ} statistics. */ - public static final String GRAPH_DIAMETER_ANGLE_XZ = "Longest shortest path: Extension angle XZ"; - /** Flag for {@value #GRAPH_DIAMETER_ANGLE_ZY} statistics. */ - public static final String GRAPH_DIAMETER_ANGLE_ZY = "Longest shortest path: Extension angle ZY"; - // volume and surface - /** Flag for {@value #VOLUME} statistics. */ - public static final String VOLUME = "Volume"; - /** Flag for {@value #BRANCH_VOLUME} statistics. */ - public static final String BRANCH_VOLUME = "Branch volume"; - /** Flag for {@value #PATH_VOLUME} statistics. */ - public static final String PATH_VOLUME = "Path volume"; - /** Flag for {@value #SURFACE_AREA} statistics. */ - public static final String SURFACE_AREA = "Surface area"; - /** Flag for {@value #BRANCH_SURFACE_AREA} statistics. */ - public static final String BRANCH_SURFACE_AREA = "Branch surface area"; - /** Flag for {@value #PATH_SURFACE_AREA} statistics. */ - public static final String PATH_SURFACE_AREA = "Path surface area"; - // Strahler - /** Flag specifying {@link StrahlerAnalyzer#getRootNumber() Horton-Strahler number} statistics */ - public static final String STRAHLER_NUMBER = "Horton-Strahler root number"; - /** Flag specifying {@link StrahlerAnalyzer#getAvgBifurcationRatio() Horton-Strahler bifurcation ratio} statistics */ - public static final String STRAHLER_RATIO = "Horton-Strahler bifurcation ratio"; - // Sholl - /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getMean() Sholl mean} statistics */ - public static final String SHOLL_MEAN_VALUE = "Sholl: " + ShollAnalyzer.MEAN; - /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getSum() Sholl sum} statistics */ - public static final String SHOLL_SUM_VALUE = "Sholl: " + ShollAnalyzer.SUM; - /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getMax() Sholl max} statistics */ - public static final String SHOLL_MAX_VALUE = "Sholl: " + ShollAnalyzer.MAX; - /** Flag specifying {@value #SHOLL_N_MAX} statistics */ - public static final String SHOLL_N_MAX = "Sholl: " + ShollAnalyzer.N_MAX; - /** Flag specifying {@value #SHOLL_N_SECONDARY_MAX} statistics */ - public static final String SHOLL_N_SECONDARY_MAX = "Sholl: " + ShollAnalyzer.N_SECONDARY_MAX; - /** Flag specifying {@value #SHOLL_DECAY} statistics */ - public static final String SHOLL_DECAY = "Sholl: " + ShollAnalyzer.DECAY; - /** Flag specifying {@value #SHOLL_MAX_FITTED} statistics */ - public static final String SHOLL_MAX_FITTED = "Sholl: " + ShollAnalyzer.MAX_FITTED; - /** Flag specifying {@value #SHOLL_MAX_FITTED_RADIUS} statistics */ - public static final String SHOLL_MAX_FITTED_RADIUS = "Sholl: " + ShollAnalyzer.MAX_FITTED_RADIUS; - /** Flag specifying {@value #SHOLL_POLY_FIT_DEGREE} statistics */ - public static final String SHOLL_POLY_FIT_DEGREE = "Sholl: " + ShollAnalyzer.POLY_FIT_DEGREE; - /** Flag specifying {@value #SHOLL_KURTOSIS} statistics */ - public static final String SHOLL_KURTOSIS = "Sholl: " + ShollAnalyzer.KURTOSIS; - /** Flag specifying {@value #SHOLL_SKEWENESS} statistics */ - public static final String SHOLL_SKEWENESS = "Sholl: " + ShollAnalyzer.SKEWENESS; - /** Flag specifying {@value #SHOLL_RAMIFICATION_INDEX} statistics */ - public static final String SHOLL_RAMIFICATION_INDEX = "Sholl: " + ShollAnalyzer.RAMIFICATION_INDEX; - //convex hull - /** Flag specifying {@value #CONVEX_HULL_BOUNDARY_SIZE} statistics */ - public static final String CONVEX_HULL_BOUNDARY_SIZE = "Convex hull: " + ConvexHullAnalyzer.BOUNDARY_SIZE; - /** Flag specifying {@value #CONVEX_HULL_SIZE} statistics */ - public static final String CONVEX_HULL_SIZE = "Convex hull: " + ConvexHullAnalyzer.SIZE; - /** Flag specifying {@value #CONVEX_HULL_BOXIVITY} statistics */ - public static final String CONVEX_HULL_BOXIVITY= "Convex hull: " + ConvexHullAnalyzer.BOXIVITY; - /** Flag specifying {@value #CONVEX_HULL_ELONGATION} statistics */ - public static final String CONVEX_HULL_ELONGATION= "Convex hull: " + ConvexHullAnalyzer.ELONGATION; - /** Flag specifying {@value #CONVEX_HULL_ROUNDNESS} statistics */ - public static final String CONVEX_HULL_ROUNDNESS= "Convex hull: " + ConvexHullAnalyzer.ROUNDNESS; - /** Flag specifying {@value #CONVEX_HULL_CENTROID_ROOT_DISTANCE} statistics */ - public static final String CONVEX_HULL_CENTROID_ROOT_DISTANCE = "Convex hull: Centroid-root distance"; - - /** - * Flag for analysis of {@value #VALUES}, an optional numeric property that can - * be assigned to Path nodes (e.g., voxel intensities), assigned via - * {@link PathProfiler}. Note that an {@link IllegalArgumentException} is - * triggered if no values have been assigned to the tree being analyzed. - * - * @see Path#hasNodeValues() - * @see PathProfiler#assignValues() - */ - public static final String VALUES = "Node intensity values"; - - /** @deprecated Use {@link #BRANCH_CONTRACTION} or {@link #PATH_CONTRACTION} instead */ - @Deprecated - public static final String CONTRACTION = "Contraction"; - - /** @deprecated Use {@link #BRANCH_MEAN_RADIUS} or {@link #PATH_MEAN_RADIUS} instead */ - @Deprecated - public static final String MEAN_RADIUS = PATH_MEAN_RADIUS; - - /** @deprecated Use {@link #PATH_SPINE_DENSITY} instead */ - @Deprecated - public static final String AVG_SPINE_DENSITY = "Average spine/varicosity density"; - - @Deprecated - /** @deprecated Use {@link #BRANCH_FRACTAL_DIMENSION} or {@link #PATH_FRACTAL_DIMENSION} instead */ - public static final String FRACTAL_DIMENSION = "Fractal dimension"; - - private static final String[] ALL_FLAGS = { - GRAPH_DIAMETER_ANGLE_XY, GRAPH_DIAMETER_ANGLE_XZ, GRAPH_DIAMETER_ANGLE_ZY, // - BRANCH_CONTRACTION, BRANCH_FRACTAL_DIMENSION, BRANCH_LENGTH, - BRANCH_MEAN_RADIUS, BRANCH_SURFACE_AREA, BRANCH_VOLUME, COMPLEXITY_INDEX_ACI, COMPLEXITY_INDEX_DCI, - CONVEX_HULL_BOUNDARY_SIZE, CONVEX_HULL_BOXIVITY, CONVEX_HULL_CENTROID_ROOT_DISTANCE, CONVEX_HULL_ELONGATION, - CONVEX_HULL_ROUNDNESS, CONVEX_HULL_SIZE, DEPTH, // - BRANCH_EXTENSION_ANGLE_XY, BRANCH_EXTENSION_ANGLE_XZ, BRANCH_EXTENSION_ANGLE_ZY, // - INNER_EXTENSION_ANGLE_XY, INNER_EXTENSION_ANGLE_XZ, INNER_EXTENSION_ANGLE_ZY, PRIMARY_EXTENSION_ANGLE_XY, - PRIMARY_EXTENSION_ANGLE_XZ, PRIMARY_EXTENSION_ANGLE_ZY, TERMINAL_EXTENSION_ANGLE_XY, - TERMINAL_EXTENSION_ANGLE_XZ, TERMINAL_EXTENSION_ANGLE_ZY, // - GRAPH_DIAMETER, HEIGHT, INNER_LENGTH, INTER_NODE_ANGLE, INTER_NODE_DISTANCE, - INTER_NODE_DISTANCE_SQUARED, LENGTH, N_BRANCH_NODES, N_BRANCH_POINTS, N_BRANCHES, N_FITTED_PATHS, - N_INNER_BRANCHES, N_NODES, N_PATH_NODES, N_PATHS, N_PRIMARY_BRANCHES, N_SPINES, N_TERMINAL_BRANCHES, N_TIPS, - NODE_RADIUS, PARTITION_ASYMMETRY, PATH_CHANNEL, PATH_CONTRACTION, PATH_FRACTAL_DIMENSION, PATH_FRAME, - PATH_EXT_ANGLE_XY, PATH_EXT_ANGLE_XZ, PATH_EXT_ANGLE_ZY, PATH_EXT_ANGLE_REL_XY, PATH_EXT_ANGLE_REL_XZ, - PATH_EXT_ANGLE_REL_ZY, PATH_LENGTH, PATH_MEAN_RADIUS, - PATH_SPINE_DENSITY, PATH_N_SPINES, PATH_ORDER, PATH_SURFACE_AREA, PATH_VOLUME, PRIMARY_LENGTH, - REMOTE_BIF_ANGLES, SHOLL_DECAY, SHOLL_KURTOSIS, SHOLL_MAX_FITTED, SHOLL_MAX_FITTED_RADIUS, SHOLL_MAX_VALUE, - SHOLL_MEAN_VALUE, SHOLL_N_MAX, SHOLL_N_SECONDARY_MAX, SHOLL_POLY_FIT_DEGREE, SHOLL_RAMIFICATION_INDEX, - SHOLL_SKEWENESS, SHOLL_SUM_VALUE, STRAHLER_NUMBER, STRAHLER_RATIO, SURFACE_AREA, TERMINAL_LENGTH, VALUES, - VOLUME, WIDTH, X_COORDINATES, Y_COORDINATES, Z_COORDINATES }; - - protected LastDstats lastDstats; - private ConvexHullAnalyzer convexAnalyzer; - private static boolean exactMetricMatch; - - /** - * Instantiates a new instance from a collection of Paths - * - * @param tree the collection of paths to be analyzed - */ - public TreeStatistics(final Tree tree) { - super(tree); - } - - /** - * Gets the list of supported metrics. - * - * @return the list of available metrics - */ - public static List getAllMetrics() { - return Arrays.stream(ALL_FLAGS).collect(Collectors.toList()); - } - - /** - * Gets a subset of supported metrics. - * - * @param type the type. Either 'legacy' (metrics supported up to SNTv4.0.5), - * "safe" (metrics that can be computed from invalid graphs), - * 'common' (commonly used metrics), 'quick' (used by the 'quick - * measure' GUI commands), or 'all' (shortcut to {@link #getAllMetrics()}) - * @return the list metrics - */ - public static List getMetrics(final String type) { - // We could use Arrays.asList() here but that would make list immutable - String[] metrics; - switch (type) { - case "all": - measure(getAllMetrics(), true); - return getAllMetrics(); - case "legacy": - // Historical metrics up to SNTv4.0.10 - metrics = new String[] { BRANCH_LENGTH, CONTRACTION, REMOTE_BIF_ANGLES, PARTITION_ASYMMETRY, - FRACTAL_DIMENSION, INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, MEAN_RADIUS, AVG_SPINE_DENSITY, - N_BRANCH_POINTS, N_NODES, N_SPINES, NODE_RADIUS, PATH_CHANNEL, PATH_FRAME, PATH_LENGTH, PATH_ORDER, - PRIMARY_LENGTH, INNER_LENGTH, TERMINAL_LENGTH, VALUES, X_COORDINATES, Y_COORDINATES, - Z_COORDINATES }; - break; - case "deprecated": - metrics = new String[] { AVG_SPINE_DENSITY, CONTRACTION, FRACTAL_DIMENSION, MEAN_RADIUS, - AVG_SPINE_DENSITY }; - break; - case "safe": - metrics = new String[] { INTER_NODE_ANGLE, // - INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, N_BRANCH_POINTS, N_FITTED_PATHS, - N_NODES, N_PATH_NODES, N_PATHS, N_SPINES, N_TIPS, NODE_RADIUS, PATH_CHANNEL, PATH_CONTRACTION, - PATH_FRAME, PATH_EXT_ANGLE_XY, PATH_EXT_ANGLE_XZ, PATH_EXT_ANGLE_ZY, - PATH_EXT_ANGLE_REL_XY, PATH_EXT_ANGLE_REL_XZ, PATH_EXT_ANGLE_REL_ZY, - PATH_LENGTH, PATH_MEAN_RADIUS, PATH_SPINE_DENSITY, PATH_N_SPINES, PATH_ORDER, - PATH_SURFACE_AREA, PATH_VOLUME, VALUES, X_COORDINATES, Y_COORDINATES, Z_COORDINATES }; - break; - case "common": - metrics = new String[] { BRANCH_CONTRACTION, BRANCH_FRACTAL_DIMENSION, BRANCH_LENGTH, BRANCH_MEAN_RADIUS, - BRANCH_SURFACE_AREA, BRANCH_VOLUME, COMPLEXITY_INDEX_ACI, COMPLEXITY_INDEX_DCI, CONVEX_HULL_SIZE, - DEPTH, INNER_LENGTH, INTER_NODE_ANGLE, INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, LENGTH, - N_BRANCH_POINTS, N_BRANCHES, N_INNER_BRANCHES, N_NODES, N_PRIMARY_BRANCHES, N_SPINES, - N_TERMINAL_BRANCHES, N_TIPS, NODE_RADIUS, PARTITION_ASYMMETRY, PRIMARY_LENGTH, REMOTE_BIF_ANGLES, - SHOLL_DECAY, SHOLL_MAX_VALUE, SHOLL_MAX_FITTED, SHOLL_MAX_FITTED_RADIUS, SHOLL_MEAN_VALUE, - SURFACE_AREA, STRAHLER_NUMBER, TERMINAL_LENGTH, VALUES, VOLUME, X_COORDINATES, Y_COORDINATES, - Z_COORDINATES }; - break; - case "quick": - /* NB: This list can only include metrics supported by #getMetricWithoutChecks() */ - metrics = new String[] { // - LENGTH, MultiTreeStatistics.AVG_BRANCH_LENGTH, N_BRANCH_POINTS, N_TIPS, N_BRANCHES, // - N_PRIMARY_BRANCHES, N_TERMINAL_BRANCHES, // - PATH_MEAN_RADIUS, // - AVG_SPINE_DENSITY, // - STRAHLER_NUMBER, MultiTreeStatistics.HIGHEST_PATH_ORDER, - /* Disabled metrics (likely too specific or uncommon for most users) */ - // MultiTreeStatistics.ASSIGNED_VALUE, MultiTreeStatistics.AVG_CONTRACTION, - // MultiTreeStatistics.AVG_FRACTAL_DIMENSION, - // MultiTreeStatistics.AVG_FRAGMENTATION, - // MultiTreeStatistics.AVG_REMOTE_ANGLE, - // MultiTreeStatistics.AVG_PARTITION_ASYMMETRY, - // PRIMARY_LENGTH, INNER_LENGTH, TERMINAL_LENGTH, - // N_INNER_BRANCHES, - // N_FITTED_PATHS, N_PATHS, N_NODES, - // WIDTH, HEIGHT, DEPTH, - // SHOLL_DECAY, SHOLL_MAX_VALUE, - }; - break; - default: - throw new IllegalArgumentException("Unrecognized type"); - } - return Arrays.stream(metrics).collect(Collectors.toList()); - } - - /** - * Gets the list of most commonly used metrics. - * - * @return the list of commonly used metrics - * @see #getMetric(String) - */ - @Deprecated - public static List getMetrics() { - return getMetrics("common"); - } - - /** - * Computes the {@link SummaryStatistics} for the specified measurement. - * - * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) - * @return the SummaryStatistics object. - */ - public SummaryStatistics getSummaryStats(final String metric) { - final SummaryStatistics sStats = new SummaryStatistics(); - assembleStats(new StatisticsInstance(sStats), getNormalizedMeasurement(metric)); - return sStats; - } - - /** - * Computes the {@link DescriptiveStatistics} for the specified measurement. - * - * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) - * @return the DescriptiveStatistics object. - */ - public DescriptiveStatistics getDescriptiveStats(final String metric) { - final String normMeasurement = getNormalizedMeasurement(metric); - if (!lastDstatsCanBeRecycled(normMeasurement)) { - final DescriptiveStatistics dStats = new DescriptiveStatistics(); - assembleStats(new StatisticsInstance(dStats), normMeasurement); - lastDstats = new LastDstats(normMeasurement, dStats); - } - return lastDstats.dStats; - } - - /** - * Retrieves the amount of cable length present on each brain compartment - * innervated by the analyzed neuron. - * - * @param level the ontological depth of the compartments to be considered - * @return the map containing the brain compartments as keys, and cable lengths - * as values. - * @see AllenCompartment#getOntologyDepth() - */ - public Map getAnnotatedLength(final int level) { - return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.ANY_HEMISPHERE, false); - } - - /** - * Retrieves the amount of cable length present on each brain compartment - * innervated by the analyzed neuron. - * - * @param level the ontological depth of the compartments to be considered - * @param hemisphere typically 'left' or 'right'. The hemisphere flag ( - * {@link BrainAnnotation#LEFT_HEMISPHERE} or - * {@link BrainAnnotation#RIGHT_HEMISPHERE}) is extracted from - * the first character of the string (case-insensitive). - * Ignored if not a recognized option - * @return the map containing the brain compartments as keys, and cable lengths - * as values. - * @see AllenCompartment#getOntologyDepth() - */ - public Map getAnnotatedLength(final int level, final String hemisphere) { - return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.getHemisphereFlag(hemisphere), false); - } - - /** - * Retrieves the amount of cable length present on each brain compartment - * innervated by the analyzed neuron. - * - * @param level the ontological depth of the compartments to be considered - * @param hemisphere typically 'left' or 'right'. The hemisphere flag ( - * {@link BrainAnnotation#LEFT_HEMISPHERE} or - * {@link BrainAnnotation#RIGHT_HEMISPHERE}) is extracted from - * the first character of the string (case-insensitive). - * Ignored if not a recognized option - * @param norm whether length should be normalized to the cells' cable - * length - * @return the map containing the brain compartments as keys, and cable lengths - * as values. - * @see AllenCompartment#getOntologyDepth() - */ - public Map getAnnotatedLength(final int level, final String hemisphere, - final boolean norm) { - return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.getHemisphereFlag(hemisphere), norm); - } - - protected static Map getAnnotatedLength(final DirectedWeightedGraph graph, final int level, - final char lr, final boolean norm) { - final NodeStatistics nodeStats = new NodeStatistics<>(graph.vertexSet(lr)); - final Map> annotatedNodesMap = nodeStats.getAnnotatedNodes(level); - final HashMap lengthMap = new HashMap<>(); - for (final Map.Entry> entry : annotatedNodesMap.entrySet()) { - final BrainAnnotation annotation = entry.getKey(); - final Set nodeSubset = entry.getValue(); - final DirectedWeightedSubgraph subgraph = graph.getSubgraph(nodeSubset); - final double subgraphWeight = subgraph.sumEdgeWeights(true); - lengthMap.put(annotation, subgraphWeight); - } - if (norm) { - final double sumLength = graph.sumEdgeWeights(); - lengthMap.values().forEach( l -> l /= sumLength); - } - return lengthMap; - } - - public Map getAnnotatedLengthsByHemisphere(final int level) { - return getAnnotatedLengthsByHemisphere(tree.getGraph(), level, false); - } - - protected static Map getAnnotatedLengthsByHemisphere(final DirectedWeightedGraph graph, - final int level, final boolean norm) { - final char ipsiFlag = graph.getRoot().getHemisphere(); - if (ipsiFlag == BrainAnnotation.ANY_HEMISPHERE) - throw new IllegalArgumentException("Tree's root has its hemisphere flag unset"); - final char contraFlag = (ipsiFlag == BrainAnnotation.LEFT_HEMISPHERE) ? BrainAnnotation.RIGHT_HEMISPHERE - : BrainAnnotation.LEFT_HEMISPHERE; - final Map ipsiMap = getAnnotatedLength(graph, level, ipsiFlag, norm); - final Map contraMap = getAnnotatedLength(graph, level, contraFlag, norm); - final Map finalMap = new HashMap<>(); - ipsiMap.forEach((k, ipsiLength) -> { - double[] values = new double[2]; - final Double contraLength = contraMap.get(k); - values[0] = ipsiLength; - values[1] = (contraLength == null) ? 0d : contraLength; - finalMap.put(k, values); - }); - contraMap.keySet().removeIf(k -> ipsiMap.get(k) != null); - contraMap.forEach((k, contraLength) -> { - finalMap.put(k, new double[] { 0d, contraLength }); - }); - return finalMap; - } - - /** - * Retrieves the of cable length frequencies across brain areas. - * - * @return the histogram of cable length frequencies. - */ - public SNTChart getAnnotatedLengthHistogram() { - return getAnnotatedLengthHistogram(Integer.MAX_VALUE); - } - - /** - * Retrieves the histogram of cable length frequencies across brain areas of the - * specified ontology level. - * - * @param depth the ontological depth of the compartments to be considered - * @return the annotated length histogram - * @see AllenCompartment#getOntologyDepth() - */ - public SNTChart getAnnotatedLengthHistogram(final int depth) { - final Map map = getAnnotatedLength(depth); - return getAnnotatedLengthHistogram(map, depth, ""); - } - - /** - * Retrieves the histogram of cable length frequencies across brain areas of the - * specified ontology level across the specified hemisphere. - * - * @param depth the ontological depth of the compartments to be considered - * @param hemisphere 'left', 'right' or 'ratio' (case-insensitive). Ignored if - * not a recognized option - * @return the annotated length histogram - * @see AllenCompartment#getOntologyDepth() - */ - public SNTChart getAnnotatedLengthHistogram(int depth, String hemisphere) { - if ("ratio".equalsIgnoreCase(hemisphere.trim())) - return getAnnotatedLengthsByHemisphereHistogram(depth); - final Map map = getAnnotatedLength(depth, hemisphere); - - String label; - final char hemiFlag = BrainAnnotation.getHemisphereFlag(hemisphere); - switch (hemiFlag) { - case BrainAnnotation.LEFT_HEMISPHERE: - label = "Left hemi."; - break; - case BrainAnnotation.RIGHT_HEMISPHERE: - label = "Right hemi."; - break; - default: - label = ""; - } - return getAnnotatedLengthHistogram(map, depth, label); - } - - protected SNTChart getAnnotatedLengthsByHemisphereHistogram(int depth) { - final DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - Map seriesMap = getAnnotatedLengthsByHemisphere(depth); - Map undefinedLengthMap = new HashMap<>(); - seriesMap.entrySet().stream().sorted((e1, e2) -> -Double.compare(e1.getValue()[0], e2.getValue()[0])) - .forEach(entry -> { - if (entry.getKey() == null || entry.getKey().getOntologyDepth() == 0) { - // a null brain annotation or the root brain itself - undefinedLengthMap.put(entry.getKey(), entry.getValue()); - } else { - dataset.addValue(entry.getValue()[0], "Ipsilateral", entry.getKey().acronym()); - dataset.addValue(entry.getValue()[1], "Contralateral", entry.getKey().acronym()); - } - }); - - if (!undefinedLengthMap.isEmpty()) { - undefinedLengthMap.forEach( (k, v) -> { - dataset.addValue(v[0], "Ipsilateral", BrainAnnotation.simplifiedString(k)); - dataset.addValue(v[1], "Contralateral", BrainAnnotation.simplifiedString(k)); - }); - } - final String axisTitle = (depth == Integer.MAX_VALUE) ? "no filtering" : "depth \u2264" + depth; - final JFreeChart chart = AnalysisUtils.createCategoryPlot( // - "Brain areas (N=" + (seriesMap.size() - undefinedLengthMap.size()) + ", " + axisTitle + ")", // domain axis title - String.format("Cable length (%s)", getUnit()), // range axis title - "", // axis unit (already applied) - dataset, 2); - final String tLabel = (tree.getLabel() == null) ? "" : tree.getLabel(); - return new SNTChart(tLabel + " Annotated Length", chart, new Dimension(400, 600)); - } - - protected SNTChart getAnnotatedLengthHistogram(final Map map, final int depth, - final String secondaryLabel) { - final DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - Map undefinedLengthMap = new HashMap<>(); - final String seriesLabel = (depth == Integer.MAX_VALUE) ? "no filtering" : "depth \u2264" + depth; - map.entrySet().stream().sorted((e1, e2) -> -e1.getValue().compareTo(e2.getValue())).forEach(entry -> { - if (entry.getKey() == null || entry.getKey().getOntologyDepth() == 0) { - // a null brain annotation or the root brain itself - undefinedLengthMap.put(entry.getKey(), entry.getValue()); - } else - dataset.addValue(entry.getValue(), seriesLabel, entry.getKey().acronym()); - }); - if (!undefinedLengthMap.isEmpty()) { - undefinedLengthMap.forEach( (k, v) -> { - dataset.addValue(v, seriesLabel, BrainAnnotation.simplifiedString(k)); - }); - } - final JFreeChart chart = AnalysisUtils.createCategoryPlot( // - "Brain areas (N=" + (map.size()-undefinedLengthMap.size()) + ", " + seriesLabel + ")", // domain axis title - String.format("Cable length (%s)", getUnit()), // range axis title - "", // unit: already specified - dataset); - final String tLabel = (tree.getLabel() == null) ? "" : tree.getLabel(); - final SNTChart frame = new SNTChart(tLabel + " Annotated Length", chart, new Dimension(400, 600)); - if (secondaryLabel != null) - frame.annotate(secondaryLabel); - return frame; - } - - /** - * Retrieves the histogram of relative frequencies histogram for a univariate - * measurement. The number of bins is determined using the Freedman-Diaconis - * rule. - * - * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) - * @return the frame holding the histogram - */ - public SNTChart getHistogram(final String metric) { - final String normMeasurement = getNormalizedMeasurement(metric); - final HistogramDatasetPlus datasetPlus = new HDPlus(normMeasurement, true); - return getHistogram(normMeasurement, datasetPlus); - } - - public SNTChart getPolarHistogram(final String metric) { - final String normMeasurement = getNormalizedMeasurement(metric); - final HistogramDatasetPlus datasetPlus = new HDPlus(normMeasurement, true); - final JFreeChart chart = AnalysisUtils.createPolarHistogram(normMeasurement, getUnit(), lastDstats.dStats, datasetPlus); - return new SNTChart("Polar Hist. " + tree.getLabel(), chart); - } - - /** - * Assembles a Flow plot (aka Sankey diagram) for the specified feature using - * "mean" as integration statistic, and no cutoff value. - * - * @see #getFlowPlot(String, Collection, String, double, boolean) - */ - public SNTChart getFlowPlot(final String feature, final Collection annotations, - final boolean normalize) { - return getFlowPlot(feature, annotations, "sum", Double.MIN_VALUE, normalize); - } - - /** - * Assembles a Flow plot (aka Sankey diagram) for the specified feature using - * "mean" as integration statistic, no cutoff value, and all of the brain - * regions of the specified ontology depth. - * - * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, - * {@value MultiTreeStatistics#N_BRANCH_POINTS}, - * {@value MultiTreeStatistics#N_TIPS}, etc.). - * @param depth the ontological depth of the compartments to be considered - * @return the flow plot - * @see #getFlowPlot(String, Collection, String, double, boolean) - */ - public SNTChart getFlowPlot(final String feature, final int depth) { - return getFlowPlot(feature, depth, Double.MIN_VALUE, true); - } - - - /** - * Assembles a Flow plot (aka Sankey diagram) for the specified feature using - * "mean" as integration statistic, no cutoff value, and all of the brain - * regions of the specified ontology depth. * - * - * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, - * {@value MultiTreeStatistics#N_BRANCH_POINTS}, - * {@value MultiTreeStatistics#N_TIPS}, etc.) - * @param depth the ontological depth of the compartments to be considered - * @param cutoff a filtering option. If the computed {@code feature} for an - * annotation is below this value, that annotation is excluded - * from the plot * @param normalize If true, values are retrieved - * as ratios. E.g., If {@code feature} is - * {@value MultiTreeStatistics#LENGTH}, and {@code cutoff} 0.1, - * BrainAnnotations in {@code annotations} associated with less - * than 10% of cable length are ignored. - * @return the flow plot - */ - public SNTChart getFlowPlot(final String feature, final int depth, final double cutoff, final boolean normalize) { - return getFlowPlot(feature, getAnnotations(depth), "sum", cutoff, normalize); - } - - /** - * Assembles a Flow plot (aka Sankey diagram) for the specified feature using - * "mean" as integration statistic, and no cutoff value. - * - * @see #getFlowPlot(String, Collection, String, double, boolean) - */ - public SNTChart getFlowPlot(final String feature, final Collection annotations) { - return getFlowPlot(feature, annotations, "sum", Double.MIN_VALUE, false); - } - - /** - * Assembles a Flow plot (aka Sankey diagram) for the specified feature. - * - * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, - * {@value MultiTreeStatistics#N_BRANCH_POINTS}, - * {@value MultiTreeStatistics#N_TIPS}, etc.). Note that the - * majority of {@link MultiTreeStatistics#getAllMetrics()} - * metrics are currently not supported. - * @param annotations the BrainAnnotations to be queried. Null not allowed. - * @param statistic the integration statistic (lower case). Either "mean", - * "sum", "min" or "max". Null not allowed. - * @param cutoff a filtering option. If the computed {@code feature} for an - * annotation is below this value, that annotation is - * excluded from the plot - * @param normalize If true, values are retrieved as ratios. E.g., If - * {@code feature} is {@value MultiTreeStatistics#LENGTH}, - * and {@code cutoff} 0.1, BrainAnnotations in - * {@code annotations} associated with less than 10% of cable - * length are ignored. - * - * @return the SNTChart holding the flow plot - */ - public SNTChart getFlowPlot(final String feature, final Collection annotations, - final String statistic, final double cutoff, final boolean normalize) { - final GroupedTreeStatistics gts = new GroupedTreeStatistics(); - gts.addGroup(Collections.singleton(getParsedTree()), (null == tree.getLabel()) ? "" : tree.getLabel()); - final SNTChart chart = gts.getFlowPlot(feature, annotations, statistic, cutoff, normalize); - chart.setTitle("Flow Plot [Single Cell]"); - return chart; - } - - public static TreeStatistics fromCollection(final Collection trees, final String metric) { - final Iterator iterator = trees.iterator(); - final TreeStatistics holder = new TreeStatistics(iterator.next()); - if (trees.size() == 1) - return holder; - holder.tree.setLabel(getLabelFromTreeCollection(trees)); - final String normMeasurement = getNormalizedMeasurement(metric); - final DescriptiveStatistics holderStats = holder.getDescriptiveStats(normMeasurement); - while (iterator.hasNext()) { - final Tree tree = iterator.next(); - final TreeStatistics treeStats = new TreeStatistics(tree); - final DescriptiveStatistics dStats = treeStats.getDescriptiveStats(normMeasurement); - for (final double v : dStats.getValues()) { - holderStats.addValue(v); - } - } - return holder; - } - - private static String getLabelFromTreeCollection(final Collection trees) { - final StringBuilder sb = new StringBuilder(); - for (final Tree tree : trees) { - if (tree.getLabel() != null) - sb.append(tree.getLabel()).append(" "); - } - return (sb.length() == 0) ? "Grouped Cells" : sb.toString().trim(); - } - - protected SNTChart getHistogram(final String normMeasurement, final HistogramDatasetPlus datasetPlus) { - final JFreeChart chart = AnalysisUtils.createHistogram(normMeasurement, getUnit(), lastDstats.dStats, datasetPlus); - return new SNTChart("Hist. " + tree.getLabel(), chart); - } - - protected static String tryReallyHardToGuessMetric(final String guess) { - final String normGuess = guess.toLowerCase(); - if (normGuess.contains("contrac")) { - return CONTRACTION; - } - if (normGuess.contains("remote") && normGuess.contains("angle")) { - return REMOTE_BIF_ANGLES; - } - if (normGuess.contains("partition") && normGuess.contains("asymmetry")) { - return PARTITION_ASYMMETRY; - } - if (normGuess.contains("fractal")) { - return FRACTAL_DIMENSION; - } - if (normGuess.contains("length") || normGuess.contains("cable")) { - if (normGuess.contains("term")) { - return TERMINAL_LENGTH; - } else if (normGuess.contains("prim")) { - return PRIMARY_LENGTH; - } else if (normGuess.contains("inner")) { - return INNER_LENGTH; - } else if (normGuess.contains("path")) { - return PATH_LENGTH; - } else { - return BRANCH_LENGTH; - } - } - if (normGuess.contains("angle")) { - if (normGuess.contains("path") && normGuess.contains("ext")) { - if (normGuess.contains("xz")) - return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_XZ : PATH_EXT_ANGLE_XZ; - else if (normGuess.contains("zy")) - return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_ZY :PATH_EXT_ANGLE_ZY; - else - return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_XY :PATH_EXT_ANGLE_XY; - } else if (normGuess.contains("term")) { - if (normGuess.contains("xz")) - return TERMINAL_EXTENSION_ANGLE_XZ; - else if (normGuess.contains("zy")) - return TERMINAL_EXTENSION_ANGLE_ZY; - else - return TERMINAL_EXTENSION_ANGLE_XY; - } else if (normGuess.contains("prim")) { - if (normGuess.contains("xz")) - return PRIMARY_EXTENSION_ANGLE_XZ; - else if (normGuess.contains("zy")) - return PRIMARY_EXTENSION_ANGLE_ZY; - else - return PRIMARY_EXTENSION_ANGLE_XY; - } else if (normGuess.contains("inner")) { - if (normGuess.contains("xz")) - return INNER_EXTENSION_ANGLE_XZ; - else if (normGuess.contains("zy")) - return INNER_EXTENSION_ANGLE_ZY; - else - return INNER_EXTENSION_ANGLE_XY; - } else if (normGuess.contains("xz")) - return BRANCH_EXTENSION_ANGLE_XZ; - else if (normGuess.contains("zy")) - return BRANCH_EXTENSION_ANGLE_ZY; - else - return BRANCH_EXTENSION_ANGLE_XY; - } - if (normGuess.contains("path") && normGuess.contains("order")) { - return PATH_ORDER; - } - if (normGuess.contains("bp") || normGuess.contains("branch points") - || normGuess.contains("junctions")) { - return N_BRANCH_POINTS; - } - if (normGuess.contains("nodes")) { - return N_NODES; - } - if (normGuess.contains("node") && (normGuess.contains("dis") || normGuess.contains("dx"))) { - if (normGuess.contains("sq")) { - return INTER_NODE_DISTANCE_SQUARED; - } else if (normGuess.contains("angle")) { - return INTER_NODE_ANGLE; - } else { - return INTER_NODE_DISTANCE; - } - } - if (normGuess.contains("radi")) { - if (normGuess.contains("mean") || normGuess.contains("avg") - || normGuess.contains("average")) { - return MEAN_RADIUS; - } else { - return NODE_RADIUS; - } - } - if (normGuess.contains("spines") || normGuess.contains("varicosities")) { - if (normGuess.contains("mean") || normGuess.contains("avg") || normGuess.contains("average") - || normGuess.contains("dens")) { - return AVG_SPINE_DENSITY; - } else { - return N_SPINES; - } - } - if (normGuess.contains("values") || normGuess.contains("intensit")) { - return VALUES; - } - if (normGuess.matches(".*\\bx\\b.*")) { - return X_COORDINATES; - } - if (normGuess.matches(".*\\by\\b.*")) { - return Y_COORDINATES; - } - if (normGuess.matches(".*\\bz\\b.*")) { - return Z_COORDINATES; - } - return "unknown"; - } - - protected static String getNormalizedMeasurement(final String measurement) { - if (isExactMetricMatch()) - return measurement; - for (final String flag: ALL_FLAGS) { - if (flag.equalsIgnoreCase(measurement)) - return flag; - } - for (final String flag : MultiTreeStatistics.getMetrics()) { - if (flag.equalsIgnoreCase(measurement)) - return flag; - } - for (final String flag : getMetrics("deprecated")) { - if (flag.equalsIgnoreCase(measurement)) - return flag; - } - final String normMeasurement = tryReallyHardToGuessMetric(measurement); - if (!measurement.equals(normMeasurement)) { - SNTUtils.log("\"" + normMeasurement + "\" assumed"); - if ("unknown".equals(normMeasurement)) { - throw new UnknownMetricException("Unrecognizable measurement! " - + "Maybe you meant one of the following?: " + Arrays.toString(ALL_FLAGS)); - } - } - return normMeasurement; - } - - protected void assembleStats(final StatisticsInstance stat, final String measurement) { - final String m = getNormalizedMeasurement(measurement); - switch (m) { - case BRANCH_CONTRACTION: - case CONTRACTION: - try { - for (final Path p : getBranches()) - stat.addValue(p.getContraction()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case BRANCH_LENGTH: - try { - for (final Path p : getBranches()) - stat.addValue(p.getLength()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case BRANCH_MEAN_RADIUS: - try { - for (final Path p : getBranches()) - stat.addValue(p.getMeanRadius()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case BRANCH_SURFACE_AREA: - try { - for (final Path p : getBranches()) - stat.addValue(p.getApproximatedSurface()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case BRANCH_VOLUME: - try { - for (final Path p : getBranches()) - stat.addValue(p.getApproximatedVolume()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case COMPLEXITY_INDEX_ACI: - // implementation: doi: 10.1523/JNEUROSCI.4434-06.2007 - double sumPathOrders = 0; - for (final Path p : tree.list()) - sumPathOrders += p.getOrder() - 1; - stat.addValue(sumPathOrders / tree.list().size()); - break; - case COMPLEXITY_INDEX_DCI: - try { - // Implementation by chronological order: - // www.jneurosci.org/content/19/22/9928#F6 - // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3373517/ - // https://journals.physiology.org/doi/full/10.1152/jn.00829.2011 - final DirectedWeightedGraph graph = tree.getGraph(); - final List graphTips = graph.getTips(); - final SWCPoint root = graph.getRoot(); - double sumBranchTipOrders = 0; - for (final SWCPoint tip : graphTips) { - for (final SWCPoint vx : graph.getShortestPathVertices(root, tip)) { - if (graph.outDegreeOf(vx) > 1) - sumBranchTipOrders++; - } - } - stat.addValue((sumBranchTipOrders + graphTips.size()) * getCableLength() / getPrimaryBranches().size()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case CONVEX_HULL_BOUNDARY_SIZE: - case CONVEX_HULL_BOXIVITY: - case CONVEX_HULL_ELONGATION: - case CONVEX_HULL_ROUNDNESS: - case CONVEX_HULL_SIZE: - stat.addValue(getConvexHullMetric(m)); - break; - case CONVEX_HULL_CENTROID_ROOT_DISTANCE: - final PointInImage root = tree.getRoot(); - if (root == null) - stat.addValue(Double.NaN); - else - stat.addValue(getConvexAnalyzer().getCentroid().distanceTo(root)); - break; - case DEPTH: - stat.addValue(getDepth()); - break; - case BRANCH_FRACTAL_DIMENSION: - case FRACTAL_DIMENSION: - try { - getFractalDimension().forEach(stat::addValue); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case PATH_FRACTAL_DIMENSION: - tree.list().forEach(p -> stat.addValue(p.getFractalDimension())); - break; - case GRAPH_DIAMETER: - try { - stat.addValue(tree.getGraph().getLongestPath(true).getLength()); - } catch (final IllegalArgumentException ignored) { - stat.addValue(Double.NaN); - } - break; - case GRAPH_DIAMETER_ANGLE_XY: - try { - stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleXY(false)); - } catch (final IllegalArgumentException ignored) { - stat.addValue(Double.NaN); - } - break; - case GRAPH_DIAMETER_ANGLE_XZ: - try { - stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleXZ(false)); - } catch (final IllegalArgumentException ignored) { - stat.addValue(Double.NaN); - } - break; - case GRAPH_DIAMETER_ANGLE_ZY: - try { - stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleZY(false)); - } catch (final IllegalArgumentException ignored) { - stat.addValue(Double.NaN); - } - break; - case HEIGHT: - stat.addValue(getHeight()); - break; - case INNER_LENGTH: - try { - getInnerBranches().forEach( b -> stat.addValue(b.getLength())); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case BRANCH_EXTENSION_ANGLE_XY: - try { - getBranches().forEach( b -> stat.addValue(b.getExtensionAngleXY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case BRANCH_EXTENSION_ANGLE_XZ: - try { - getBranches().forEach( b -> stat.addValue(b.getExtensionAngleXZ(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case BRANCH_EXTENSION_ANGLE_ZY: - try { - getBranches().forEach( b -> stat.addValue(b.getExtensionAngleZY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case INNER_EXTENSION_ANGLE_XY: - try { - getInnerBranches().forEach( b -> stat.addValue(b.getExtensionAngleXY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case INNER_EXTENSION_ANGLE_XZ: - try { - getInnerBranches().forEach( b -> stat.addValue(b.getExtensionAngleXZ(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case INNER_EXTENSION_ANGLE_ZY: - try { - getInnerBranches().forEach( b -> stat.addValue(b.getExtensionAngleZY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case PRIMARY_EXTENSION_ANGLE_XY: - try { - getPrimaryBranches().forEach( b -> stat.addValue(b.getExtensionAngleXY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case PRIMARY_EXTENSION_ANGLE_XZ: - try { - getPrimaryBranches().forEach( b -> stat.addValue(b.getExtensionAngleXZ(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case PRIMARY_EXTENSION_ANGLE_ZY: - try { - getPrimaryBranches().forEach( b -> stat.addValue(b.getExtensionAngleZY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case TERMINAL_EXTENSION_ANGLE_XY: - try { - getTerminalBranches().forEach( b -> stat.addValue(b.getExtensionAngleXY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case TERMINAL_EXTENSION_ANGLE_XZ: - try { - getTerminalBranches().forEach( b -> stat.addValue(b.getExtensionAngleXZ(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case TERMINAL_EXTENSION_ANGLE_ZY: - try { - getTerminalBranches().forEach( b -> stat.addValue(b.getExtensionAngleZY(false))); - } catch (final IllegalArgumentException ignored ) { - stat.addValue(Double.NaN); - } - break; - case INTER_NODE_ANGLE: - for (final Path p : tree.list()) { - if (p.size() < 3) - continue; - for (int i = 2; i < p.size(); i++) { - stat.addValue(p.getAngle(i)); - } - } - break; - case INTER_NODE_DISTANCE: - for (final Path p : tree.list()) { - if (p.size() < 2) - continue; - for (int i = 1; i < p.size(); i += 1) { - stat.addValue(p.getNode(i).distanceTo(p.getNode(i - 1))); - } - } - break; - case INTER_NODE_DISTANCE_SQUARED: - for (final Path p : tree.list()) { - if (p.size() < 2) - continue; - for (int i = 1; i < p.size(); i += 1) { - stat.addValue(p.getNode(i).distanceSquaredTo(p.getNode(i - 1))); - } - } - break; - case LENGTH: - stat.addValue(getCableLength()); - break; - case N_BRANCH_NODES: - try { - getBranches().forEach(b -> stat.addValue(b.size())); - } catch (final IllegalArgumentException ignored) { - stat.addValue(Double.NaN); - } - break; - case N_BRANCH_POINTS: - stat.addValue(getBranchPoints().size()); - break; - case N_BRANCHES: - stat.addValue(getNBranches()); - break; - case N_FITTED_PATHS: - stat.addValue(getNFittedPaths()); - break; - case N_INNER_BRANCHES: - stat.addValue(getInnerBranches().size()); - break; - case N_NODES: - stat.addValue(getNNodes()); - break; - case N_PATH_NODES: - tree.list().forEach(path -> stat.addValue(path.size())); - break; - case N_PATHS: - stat.addValue(getNPaths()); - break; - case N_PRIMARY_BRANCHES: - stat.addValue(getPrimaryBranches().size()); - break; - case N_SPINES: - stat.addValue(getNoSpinesOrVaricosities()); - break; - case N_TERMINAL_BRANCHES: - stat.addValue(getTerminalBranches().size()); - break; - case N_TIPS: - stat.addValue(getTips().size()); - break; - case NODE_RADIUS: - for (final Path p : tree.list()) { - for (int i = 0; i < p.size(); i++) { - stat.addValue(p.getNodeRadius(i)); - } - } - break; - case PARTITION_ASYMMETRY: - try { - for (final double asymmetry : getPartitionAsymmetry()) - stat.addValue(asymmetry); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case PATH_CHANNEL: - for (final Path p : tree.list()) { - stat.addValue(p.getChannel()); - } - break; - case PATH_CONTRACTION: - try { - for (final Path p : tree.list()) - stat.addValue(p.getContraction()); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case PATH_FRAME: - for (final Path p : tree.list()) { - stat.addValue(p.getFrame()); - } - break; - case PATH_EXT_ANGLE_XY: - case PATH_EXT_ANGLE_REL_XY: - for (final Path p : tree.list()) - stat.addValue(p.getExtensionAngleXY(PATH_EXT_ANGLE_REL_XY.equals(m))); - break; - case PATH_EXT_ANGLE_XZ: - case PATH_EXT_ANGLE_REL_XZ: - for (final Path p : tree.list()) - stat.addValue(p.getExtensionAngleXZ(PATH_EXT_ANGLE_REL_XZ.equals(m))); - break; - case PATH_EXT_ANGLE_ZY: - case PATH_EXT_ANGLE_REL_ZY: - for (final Path p : tree.list()) - stat.addValue(p.getExtensionAngleZY(PATH_EXT_ANGLE_REL_ZY.equals(m))); - break; - case PATH_LENGTH: - for (final Path p : tree.list()) - stat.addValue(p.getLength()); - break; - case PATH_MEAN_RADIUS: - for (final Path p : tree.list()) { - stat.addValue(p.getMeanRadius()); - } - break; - case PATH_SPINE_DENSITY: - case AVG_SPINE_DENSITY: - for (final Path p : tree.list()) { - stat.addValue(p.getSpineOrVaricosityCount() / p.getLength()); - } - break; - case PATH_SURFACE_AREA: - for (final Path p : tree.list()) - stat.addValue(p.getApproximatedSurface()); - break; - case PATH_VOLUME: - for (final Path p : tree.list()) - stat.addValue(p.getApproximatedVolume()); - break; - case PATH_N_SPINES: - for (final Path p : tree.list()) { - stat.addValue(p.getSpineOrVaricosityCount()); - } - break; - case PATH_ORDER: - for (final Path p : tree.list()) { - stat.addValue(p.getOrder()); - } - break; - case PRIMARY_LENGTH: - for (final Path p : getPrimaryBranches()) - stat.addValue(p.getLength()); - break; - case REMOTE_BIF_ANGLES: - try { - for (final double angle : getRemoteBifAngles()) - stat.addValue(angle); - } catch (final IllegalArgumentException ignored) { - SNTUtils.log("Error: " + ignored.getMessage()); - stat.addValue(Double.NaN); - } - break; - case SHOLL_DECAY: - case SHOLL_KURTOSIS: - case SHOLL_MAX_FITTED: - case SHOLL_MAX_FITTED_RADIUS: - case SHOLL_MAX_VALUE: - case SHOLL_MEAN_VALUE: - case SHOLL_N_MAX: - case SHOLL_N_SECONDARY_MAX: - case SHOLL_POLY_FIT_DEGREE: - case SHOLL_RAMIFICATION_INDEX: - case SHOLL_SKEWENESS: - case SHOLL_SUM_VALUE: - stat.addValue(getShollMetric(m).doubleValue()); - break; - case STRAHLER_NUMBER: - stat.addValue(getStrahlerNumber()); - break; - case STRAHLER_RATIO: - stat.addValue(getStrahlerBifurcationRatio()); - break; - case SURFACE_AREA: - stat.addValue(tree.getApproximatedSurface()); - break; - case TERMINAL_LENGTH: - for (final Path p : getTerminalBranches()) - stat.addValue(p.getLength()); - break; - case VALUES: - for (final Path p : tree.list()) { - if (!p.hasNodeValues()) - continue; - for (int i = 0; i < p.size(); i++) { - stat.addValue(p.getNodeValue(i)); - } - } - if (stat.getN() == 0) - throw new IllegalArgumentException("Tree has no values assigned"); - break; - case VOLUME: - stat.addValue(tree.getApproximatedVolume()); - break; - case WIDTH: - stat.addValue(getWidth()); - break; - case X_COORDINATES: - for (final Path p : tree.list()) { - for (int i = 0; i < p.size(); i++) { - stat.addValue(p.getNode(i).x); - } - } - break; - case Y_COORDINATES: - for (final Path p : tree.list()) { - for (int i = 0; i < p.size(); i++) { - stat.addValue(p.getNode(i).y); - } - } - break; - case Z_COORDINATES: - for (final Path p : tree.list()) { - for (int i = 0; i < p.size(); i++) { - stat.addValue(p.getNode(i).z); - } - } - break; - default: - throw new IllegalArgumentException("Unrecognized parameter " + measurement); - } - } - - protected boolean lastDstatsCanBeRecycled(final String normMeasurement) { - return (lastDstats != null && tree.size() == lastDstats.size && normMeasurement.equals(lastDstats.measurement)); - } - - class LastDstats { - - private final String measurement; - final DescriptiveStatistics dStats; - private final int size; - - LastDstats(final String measurement, final DescriptiveStatistics dStats) { - this.measurement = measurement; - this.dStats = dStats; - size = tree.size(); - } - } - - static class StatisticsInstance { - - private SummaryStatistics sStatistics; - private DescriptiveStatistics dStatistics; - - StatisticsInstance(final SummaryStatistics sStatistics) { - this.sStatistics = sStatistics; - } - - StatisticsInstance(final DescriptiveStatistics dStatistics) { - this.dStatistics = dStatistics; - } - - void addValue(final double value) { - if (sStatistics != null) - sStatistics.addValue(value); - else - dStatistics.addValue(value); - } - - long getN() { - return (sStatistics != null) ? sStatistics.getN() : dStatistics.getN(); - } - - } - - class HDPlus extends HistogramDatasetPlus { - final String measurement; - - HDPlus(final String measurement) { - this(measurement, true); - } - - HDPlus(final String measurement, final boolean retrieveValues) { - super(); - this.measurement = measurement; - if (retrieveValues) { - getDescriptiveStats(measurement); - for (final double v : lastDstats.dStats.getValues()) { - values.add(v); - } - } - } - } - - public NodeStatistics getNodeStatistics() { - return getNodeStatistics("all"); - } - - public NodeStatistics getNodeStatistics(final String type) { - return tree.getGraph().getNodeStatistics(type); - } - - public double getConvexHullMetric(final String metric) { - final String fMetric = metric.substring(metric.indexOf("Convex Hull: ") + 13).trim(); - return getConvexAnalyzer().get(fMetric); - } - - /** - * Gets the {@link ConvexHullAnalyzer} instance associated with this analyzer. - * - * @return the ConvexHullAnalyzer instance - */ - public ConvexHullAnalyzer getConvexAnalyzer() { - if (convexAnalyzer == null) { - convexAnalyzer = new ConvexHullAnalyzer(tree); - convexAnalyzer.setContext((getContext() == null) ? SNTUtils.getContext() : getContext()); - } - return convexAnalyzer; - } - - @Override - public void resetRestrictions() { - convexAnalyzer = null; - super.resetRestrictions(); - - } - - public static boolean isExactMetricMatch() { - return exactMetricMatch; - } - - public static void setExactMetricMatch(final boolean exactMetricMatch) { - TreeStatistics.exactMetricMatch = exactMetricMatch; - } - - - /* IDE debug method */ - public static void main(final String[] args) { - final MouseLightLoader loader = new MouseLightLoader("AA0015"); - final Tree axon = loader.getTree("axon"); - final TreeStatistics tStats = new TreeStatistics(axon); - final int depth = 6;// Integer.MAX_VALUE; - - // retrieve some metrics: - tStats.getHistogram("fractal dimension").show(); - NodeStatistics nStats = new NodeStatistics<>(tStats.getTips()); - SNTChart hist = nStats.getAnnotatedHistogram(depth); - hist.annotate("No. of tips: " + tStats.getTips().size()); - hist.show(); - - // retrieve annotated lengths - // AllenUtils.assignHemisphereTags(axon.getGraph()); - hist = tStats.getAnnotatedLengthHistogram(depth); - AllenCompartment somaCompartment = loader.getSomaCompartment(); - if (somaCompartment.getOntologyDepth() > depth) - somaCompartment = somaCompartment.getAncestor(depth - somaCompartment.getOntologyDepth()); - hist.annotateCategory(somaCompartment.acronym(), "soma"); - hist.show(); - hist = tStats.getAnnotatedLengthHistogram(depth, "left"); - hist.annotateCategory(somaCompartment.acronym(), "soma"); - hist.show(); - hist = tStats.getAnnotatedLengthHistogram(depth, "right"); - hist.annotateCategory(somaCompartment.acronym(), "soma"); - hist.show(); - hist = tStats.getAnnotatedLengthHistogram(depth, "ratio"); - hist.annotateCategory(somaCompartment.acronym(), "soma"); - hist.setFontSize(25); - hist.show(); - hist = tStats.getFlowPlot("Cable length", tStats.getAnnotatedLength(depth).keySet()); - hist.show(); - } +public class TreeStatistics extends ContextCommand { + + // branch angles + /** Flag specifying Branch extension angle XY */ + public static final String BRANCH_EXTENSION_ANGLE_XY = "Branch extension angle XY"; + /** Flag specifying Branch extension angle XZ */ + public static final String BRANCH_EXTENSION_ANGLE_XZ = "Branch extension angle XZ"; + /** Flag specifying Branch extension angle ZY */ + public static final String BRANCH_EXTENSION_ANGLE_ZY = "Branch extension angle ZY"; + /** Flag specifying "Inner branches: Extension angle XY" */ + public static final String INNER_EXTENSION_ANGLE_XY = "Inner branches: Extension angle XY"; + /** Flag specifying "Inner branches: Extension angle XZ" */ + public static final String INNER_EXTENSION_ANGLE_XZ = "Inner branches: Extension angle XZ"; + /** Flag specifying "Inner branches: Extension angle ZY" */ + public static final String INNER_EXTENSION_ANGLE_ZY = "Inner branches: Extension angle ZY"; + /** Flag specifying "Primary branches: Extension angle XY" */ + public static final String PRIMARY_EXTENSION_ANGLE_XY = "Primary branches: Extension angle XY"; + /** Flag specifying "Primary branches: Extension angle XZ" */ + public static final String PRIMARY_EXTENSION_ANGLE_XZ = "Primary branches: Extension angle XZ"; + /** Flag specifying "Primary branches: Extension angle ZY" */ + public static final String PRIMARY_EXTENSION_ANGLE_ZY = "Primary branches: Extension angle ZY"; + /** Flag specifying "Terminal branches: Extension angle XY" */ + public static final String TERMINAL_EXTENSION_ANGLE_XY = "Terminal branches: Extension angle XY"; + /** Flag specifying "Terminal branches: Extension angle XZ" */ + public static final String TERMINAL_EXTENSION_ANGLE_XZ = "Terminal branches: Extension angle XZ"; + /** Flag specifying "Terminal branches: Extension angle ZY" */ + public static final String TERMINAL_EXTENSION_ANGLE_ZY = "Terminal branches: Extension angle ZY"; + /** Flag specifying "Remote bifurcation angles" */ + public static final String REMOTE_BIF_ANGLES = "Remote bif. angles"; + // paths + /** Flag specifying "Path length" */ + public static final String PATH_LENGTH = "Path length"; + /** Flag specifying "Path extension angle XY" */ + public static final String PATH_EXT_ANGLE_XY = "Path extension angle XY"; + /** Flag specifying "Path extension angle XZ" */ + public static final String PATH_EXT_ANGLE_XZ = "Path extension angle XZ"; + /** Flag specifying "Path extension angle ZY" */ + public static final String PATH_EXT_ANGLE_ZY = "Path extension angle ZY"; + /** Flag specifying "Path extension angle XY (Rel.)"*/ + public static final String PATH_EXT_ANGLE_REL_XY = "Path extension angle XY (Rel.)"; + /** Flag specifying "Path extension angle XZ (Rel.)"*/ + public static final String PATH_EXT_ANGLE_REL_XZ = "Path extension angle XZ (Rel.)"; + /** Flag specifying "Path extension angle ZY (Rel.)"*/ + public static final String PATH_EXT_ANGLE_REL_ZY = "Path extension angle ZY (Rel.)"; + /** Flag specifying "Path order" */ + public static final String PATH_ORDER = "Path order"; + /** Flag specifying "Path channel" */ + public static final String PATH_CHANNEL = "Path channel"; + /** Flag specifying "Path frame" */ + public static final String PATH_FRAME = "Path frame"; + /** Flag specifying "Path mean radius" */ + public static final String PATH_MEAN_RADIUS = "Path mean radius"; + /** Flag specifying "Path spine/varicosity density" */ + public static final String PATH_SPINE_DENSITY = "Path spine/varicosity density"; + /** Flag specifying "Path contraction" */ + public static final String PATH_CONTRACTION = "Path contraction"; + /** Flag specifying "Path fractal dimension" */ + public static final String PATH_FRACTAL_DIMENSION = "Path fractal dimension"; + // branches + /** Flag specifying "Branch length" */ + public static final String BRANCH_LENGTH = "Branch length"; + /** Flag specifying "Branch mean radius" */ + public static final String BRANCH_MEAN_RADIUS = "Branch mean radius"; + /** Flag specifying "Terminal branches: Length" */ + public static final String TERMINAL_LENGTH = "Terminal branches: Length"; + /** Flag specifying "Primary branches: Length" */ + public static final String PRIMARY_LENGTH = "Primary branches: Length"; + /** Flag specifying "Inner branches: Length" */ + public static final String INNER_LENGTH = "Inner branches: Length"; + /** Flag specifying "Branch contraction" */ + public static final String BRANCH_CONTRACTION = "Branch contraction"; + /** Flag specifying "Branch fractal dimension" */ + public static final String BRANCH_FRACTAL_DIMENSION = "Branch fractal dimension"; + // nodes + /** Flag specifying "Node radius" */ + public static final String NODE_RADIUS = "Node radius"; + /** Flag specifying "Internode angle" */ + public static final String INTER_NODE_ANGLE = "Internode angle"; + /** Flag specifying "Internode distance" */ + public static final String INTER_NODE_DISTANCE = "Internode distance"; + /** Flag specifying "Internode distance (squared)" */ + public static final String INTER_NODE_DISTANCE_SQUARED = "Internode distance (squared)"; + // counts + /** Flag specifying "No. of branch points" */ + public static final String N_BRANCH_POINTS = "No. of branch points"; + /** Flag specifying "No. of nodes" */ + public static final String N_NODES = "No. of nodes"; + /** Flag specifying "No. of path nodes (path fragmentation)" */ + public static final String N_PATH_NODES = "No. of path nodes (path fragmentation)"; + /** Flag specifying "No. of branch nodes (branch fragmentation)" */ + public static final String N_BRANCH_NODES = "No. of branch nodes (branch fragmentation)"; + /** Flag specifying "No. of paths" */ + public static final String N_PATHS = "No. of paths"; + /** Flag specifying "No. of spines/varicosities" */ + public static final String N_SPINES = "No. of spines/varicosities"; + /** Flag specifying "No. of branches" */ + public static final String N_BRANCHES = "No. of branches"; + /** Flag specifying "No. of primary branches" */ + public static final String N_PRIMARY_BRANCHES = "No. of primary branches"; + /** Flag specifying "No. of inner branches" */ + public static final String N_INNER_BRANCHES = "No. of inner branches"; + /** Flag specifying "No. of terminal branches" */ + public static final String N_TERMINAL_BRANCHES = "No. of terminal branches"; + /** Flag specifying "No. of tips" */ + public static final String N_TIPS = "No. of tips"; + /** Flag specifying "No. of fitted paths" */ + public static final String N_FITTED_PATHS = "No. of fitted paths"; + /** Flag specifying "No. of spines/varicosities per path" */ + public static final String PATH_N_SPINES = "No. of spines/varicosities per path"; + //misc + /** Flag specifying "Cable length" */ + public static final String LENGTH = "Cable length"; + /** Flag specifying "Complexity index: ACI" */ + public static final String COMPLEXITY_INDEX_ACI = "Complexity index: ACI"; + /** Flag specifying "Complexity index: DCI" */ + public static final String COMPLEXITY_INDEX_DCI = "Complexity index: DCI"; + /** Flag specifying "X coordinates" */ + public static final String X_COORDINATES = "X coordinates"; + /** Flag specifying "Y coordinates" */ + public static final String Y_COORDINATES = "Y coordinates"; + /** Flag specifying "Z coordinates" */ + public static final String Z_COORDINATES = "Z coordinates"; + /** Flag specifying "Width" */ + public static final String WIDTH = "Width"; + /** Flag specifying "Height" */ + public static final String HEIGHT = "Height"; + /** Flag specifying "Depth" */ + public static final String DEPTH = "Depth"; + /** Flag specifying "Partition asymmetry" */ + public static final String PARTITION_ASYMMETRY = "Partition asymmetry"; + // graph geodesics + /** Flag specifying "Longest shortest path: Length" statistics. */ + public static final String GRAPH_DIAMETER = "Longest shortest path: Length"; + /** Flag specifying "Longest shortest path: Extension angle XY" statistics. */ + public static final String GRAPH_DIAMETER_ANGLE_XY = "Longest shortest path: Extension angle XY"; + /** Flag specifying "Longest shortest path: Extension angle XZ" statistics. */ + public static final String GRAPH_DIAMETER_ANGLE_XZ = "Longest shortest path: Extension angle XZ"; + /** Flag specifying "Longest shortest path: Extension angle ZY" statistics. */ + public static final String GRAPH_DIAMETER_ANGLE_ZY = "Longest shortest path: Extension angle ZY"; + // volume and surface + /** Flag specifying Volume statistics. */ + public static final String VOLUME = "Volume"; + /** Flag specifying Branch volume statistics. */ + public static final String BRANCH_VOLUME = "Branch volume"; + /** Flag specifying Path volume statistics. */ + public static final String PATH_VOLUME = "Path volume"; + /** Flag specifying Surface area statistics. */ + public static final String SURFACE_AREA = "Surface area"; + /** Flag specifying Branch surface area statistics. */ + public static final String BRANCH_SURFACE_AREA = "Branch surface area"; + /** Flag specifying Path surface area statistics. */ + public static final String PATH_SURFACE_AREA = "Path surface area"; + // Strahler + /** Flag specifying "Horton-Strahler root number" statistics. */ + public static final String STRAHLER_NUMBER = "Horton-Strahler root number"; + /** Flag specifying {@link StrahlerAnalyzer#getAvgBifurcationRatio() Horton-Strahler bifurcation ratio} statistics */ + public static final String STRAHLER_RATIO = "Horton-Strahler bifurcation ratio"; + // Sholl + /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getMean() Sholl mean} statistics */ + public static final String SHOLL_MEAN_VALUE = "Sholl: " + ShollAnalyzer.MEAN; + /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getSum() Sholl sum} statistics */ + public static final String SHOLL_SUM_VALUE = "Sholl: " + ShollAnalyzer.SUM; + /** Flag specifying {@link sc.fiji.snt.analysis.sholl.math.LinearProfileStats#getMax() Sholl max} statistics */ + public static final String SHOLL_MAX_VALUE = "Sholl: " + ShollAnalyzer.MAX; + /** Flag specifying "Sholl: No. maxima" statistics */ + public static final String SHOLL_N_MAX = "Sholl: " + ShollAnalyzer.N_MAX; + /** Flag specifying "Sholl: No. secondary maxima" statistics */ + public static final String SHOLL_N_SECONDARY_MAX = "Sholl: " + ShollAnalyzer.N_SECONDARY_MAX; + /** Flag specifying "Sholl: Decay" statistics */ + public static final String SHOLL_DECAY = "Sholl: " + ShollAnalyzer.DECAY; + /** Flag specifying "Sholl: Max (fitted)" statistics */ + public static final String SHOLL_MAX_FITTED = "Sholl: " + ShollAnalyzer.MAX_FITTED; + /** Flag specifying "Sholl: Max (fitted) radius" statistics */ + public static final String SHOLL_MAX_FITTED_RADIUS = "Sholl: " + ShollAnalyzer.MAX_FITTED_RADIUS; + /** Flag specifying "Sholl: Degree of Polynomial fit" statistics */ + public static final String SHOLL_POLY_FIT_DEGREE = "Sholl: " + ShollAnalyzer.POLY_FIT_DEGREE; + /** Flag specifying "Sholl: Kurtosis" statistics */ + public static final String SHOLL_KURTOSIS = "Sholl: " + ShollAnalyzer.KURTOSIS; + /** Flag specifying "Sholl: Skewness" statistics */ + public static final String SHOLL_SKEWNESS = "Sholl: " + ShollAnalyzer.SKEWENESS; + /** Flag specifying "Sholl: Ramification index" statistics */ + public static final String SHOLL_RAMIFICATION_INDEX = "Sholl: " + ShollAnalyzer.RAMIFICATION_INDEX; + //convex hull + /** Flag specifying "Convex hull: Boundary size" statistics */ + public static final String CONVEX_HULL_BOUNDARY_SIZE = "Convex hull: " + ConvexHullAnalyzer.BOUNDARY_SIZE; + /** Flag specifying "Convex hull: Size" statistics */ + public static final String CONVEX_HULL_SIZE = "Convex hull: " + ConvexHullAnalyzer.SIZE; + /** Flag specifying "Convex hull: Boxivity" statistics */ + public static final String CONVEX_HULL_BOXIVITY = "Convex hull: " + ConvexHullAnalyzer.BOXIVITY; + /** Flag specifying "Convex hull: Elongation" statistics */ + public static final String CONVEX_HULL_ELONGATION = "Convex hull: " + ConvexHullAnalyzer.ELONGATION; + /** Flag specifying "Convex hull: Roundness" statistics */ + public static final String CONVEX_HULL_ROUNDNESS = "Convex hull: " + ConvexHullAnalyzer.ROUNDNESS; + /** Flag specifying "Convex hull: Centroid-root distance" statistics */ + public static final String CONVEX_HULL_CENTROID_ROOT_DISTANCE = "Convex hull: Centroid-root distance"; + + /** + * Flag for analysis of Node intensity values, an optional numeric property that can + * be assigned to Path nodes (e.g., voxel intensities), assigned via + * {@link PathProfiler}. Note that an {@link IllegalArgumentException} is + * triggered if no values have been assigned to the tree being analyzed. + * + * @see Path#hasNodeValues() + * @see PathProfiler#assignValues() + */ + public static final String VALUES = "Node intensity values"; + + /** @deprecated Use {@link #BRANCH_CONTRACTION} or {@link #PATH_CONTRACTION} instead */ + @Deprecated + public static final String CONTRACTION = "Contraction"; + /** @deprecated Use {@link #BRANCH_MEAN_RADIUS} or {@link #PATH_MEAN_RADIUS} instead */ + @Deprecated + public static final String MEAN_RADIUS = PATH_MEAN_RADIUS; + /** @deprecated Use {@link #PATH_SPINE_DENSITY} instead */ + @Deprecated + public static final String AVG_SPINE_DENSITY = "Average spine/varicosity density"; + /** @deprecated Use {@link #BRANCH_FRACTAL_DIMENSION} or {@link #PATH_FRACTAL_DIMENSION} instead */ + @Deprecated + public static final String FRACTAL_DIMENSION = "Fractal dimension"; + + private static final String[] ALL_FLAGS = { // + GRAPH_DIAMETER_ANGLE_XY, GRAPH_DIAMETER_ANGLE_XZ, GRAPH_DIAMETER_ANGLE_ZY, // + BRANCH_CONTRACTION, BRANCH_FRACTAL_DIMENSION, BRANCH_LENGTH, BRANCH_MEAN_RADIUS, BRANCH_SURFACE_AREA, // + BRANCH_VOLUME, COMPLEXITY_INDEX_ACI, COMPLEXITY_INDEX_DCI, // + CONVEX_HULL_BOUNDARY_SIZE, CONVEX_HULL_BOXIVITY, CONVEX_HULL_CENTROID_ROOT_DISTANCE, CONVEX_HULL_ELONGATION, // + CONVEX_HULL_ROUNDNESS, CONVEX_HULL_SIZE, // + DEPTH, BRANCH_EXTENSION_ANGLE_XY, BRANCH_EXTENSION_ANGLE_XZ, BRANCH_EXTENSION_ANGLE_ZY, // + INNER_EXTENSION_ANGLE_XY, INNER_EXTENSION_ANGLE_XZ, INNER_EXTENSION_ANGLE_ZY, // + PRIMARY_EXTENSION_ANGLE_XY, PRIMARY_EXTENSION_ANGLE_XZ, PRIMARY_EXTENSION_ANGLE_ZY, // + TERMINAL_EXTENSION_ANGLE_XY, TERMINAL_EXTENSION_ANGLE_XZ, TERMINAL_EXTENSION_ANGLE_ZY, // + GRAPH_DIAMETER, HEIGHT, INNER_LENGTH, // + INTER_NODE_ANGLE, INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, LENGTH, // + N_BRANCH_NODES, N_BRANCH_POINTS, N_BRANCHES, N_FITTED_PATHS, N_INNER_BRANCHES, N_NODES, N_PATH_NODES, N_PATHS, // + N_PRIMARY_BRANCHES, N_SPINES, N_TERMINAL_BRANCHES, N_TIPS, NODE_RADIUS, PARTITION_ASYMMETRY, PATH_CHANNEL, // + PATH_CONTRACTION, PATH_FRACTAL_DIMENSION, PATH_FRAME, PATH_EXT_ANGLE_XY, PATH_EXT_ANGLE_XZ, PATH_EXT_ANGLE_ZY, // + PATH_EXT_ANGLE_REL_XY, PATH_EXT_ANGLE_REL_XZ, PATH_EXT_ANGLE_REL_ZY, PATH_LENGTH, PATH_MEAN_RADIUS, PATH_SPINE_DENSITY, // + PATH_N_SPINES, PATH_ORDER, PATH_SURFACE_AREA, PATH_VOLUME, PRIMARY_LENGTH, REMOTE_BIF_ANGLES, SHOLL_DECAY, // + SHOLL_KURTOSIS, SHOLL_MAX_FITTED, SHOLL_MAX_FITTED_RADIUS, SHOLL_MAX_VALUE, SHOLL_MEAN_VALUE, SHOLL_N_MAX, // + SHOLL_N_SECONDARY_MAX, SHOLL_POLY_FIT_DEGREE, SHOLL_RAMIFICATION_INDEX, SHOLL_SKEWNESS, SHOLL_SUM_VALUE, // + STRAHLER_NUMBER, STRAHLER_RATIO, SURFACE_AREA, TERMINAL_LENGTH, VALUES, VOLUME, WIDTH, X_COORDINATES, // + Y_COORDINATES, Z_COORDINATES// + }; + + private static boolean exactMetricMatch; + @Parameter + protected StatusService statusService; + @Parameter + protected DisplayService displayService; + protected Tree tree; + protected List primaryBranches; + protected List innerBranches; + protected List terminalBranches; + protected Set tips; + protected DefaultGenericTable table; + protected LastDstats lastDstats; + private Tree unfilteredTree; + private Set joints; + private String tableTitle; + private StrahlerAnalyzer sAnalyzer; + private ShollAnalyzer shllAnalyzer; + private int fittedPathsCounter = 0; + private final int unfilteredPathsFittedPathsCounter; + private ConvexHullAnalyzer convexAnalyzer; + + + /** + * Instantiates a new instance from a collection of Paths + * + * @param tree the collection of paths to be analyzed + */ + public TreeStatistics(final Tree tree) { + this.tree = tree; + unfilteredPathsFittedPathsCounter = 0; + fittedPathsCounter = 0; + } + + /** + * Instantiates Tree statistics from a collection of paths. + * + * @param paths Collection of Paths to be analyzed. Note that null Paths are + * discarded. Also, when a Path has been fitted and + * {@link Path#getUseFitted()} is true, its fitted 'flavor' is used. + * @param label the label describing the path collection + * @see #getParsedTree() + */ + public TreeStatistics(final Collection paths, String label) { + tree = new Tree(paths); + tree.setLabel(label); + for (final Path p : tree.list()) { + // If fitted flavor of path exists use it instead + if (p.getUseFitted()) { + fittedPathsCounter++; + } + } + unfilteredPathsFittedPathsCounter = fittedPathsCounter; + } + + /** + * Gets the list of supported metrics. + * + * @return the list of available metrics + */ + public static List getAllMetrics() { + return Arrays.stream(ALL_FLAGS).collect(Collectors.toList()); + } + + /** + * Gets a subset of supported metrics. + * + * @param type the type. Either 'legacy' (metrics supported up to SNTv4.0.5), + * "safe" (metrics that can be computed from invalid graphs), + * 'common' (commonly used metrics), 'quick' (used by the 'quick + * measure' GUI commands), or 'all' (shortcut to {@link #getAllMetrics()}) + * @return the list metrics + */ + public static List getMetrics(final String type) { + // We could use Arrays.asList() here but that would make list immutable + String[] metrics; + switch (type) { + case "all": + return getAllMetrics(); + case "legacy": + // Historical metrics up to SNTv4.0.10 + metrics = new String[]{BRANCH_LENGTH, CONTRACTION, REMOTE_BIF_ANGLES, PARTITION_ASYMMETRY, FRACTAL_DIMENSION, // + INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, MEAN_RADIUS, AVG_SPINE_DENSITY, N_BRANCH_POINTS, // + N_NODES, N_SPINES, NODE_RADIUS, PATH_CHANNEL, PATH_FRAME, PATH_LENGTH, PATH_ORDER, PRIMARY_LENGTH, // + INNER_LENGTH, TERMINAL_LENGTH, VALUES, X_COORDINATES, Y_COORDINATES, Z_COORDINATES}; + break; + case "deprecated": + metrics = new String[]{AVG_SPINE_DENSITY, CONTRACTION, FRACTAL_DIMENSION, MEAN_RADIUS, AVG_SPINE_DENSITY}; + break; + case "safe": + metrics = new String[]{INTER_NODE_ANGLE, // + INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, N_BRANCH_POINTS, N_FITTED_PATHS, N_NODES, // + N_PATH_NODES, N_PATHS, N_SPINES, N_TIPS, NODE_RADIUS, PATH_CHANNEL, PATH_CONTRACTION, PATH_FRAME, // + PATH_EXT_ANGLE_XY, PATH_EXT_ANGLE_XZ, PATH_EXT_ANGLE_ZY, PATH_EXT_ANGLE_REL_XY, PATH_EXT_ANGLE_REL_XZ, // + PATH_EXT_ANGLE_REL_ZY, PATH_LENGTH, PATH_MEAN_RADIUS, PATH_SPINE_DENSITY, PATH_N_SPINES, PATH_ORDER, // + PATH_SURFACE_AREA, PATH_VOLUME, VALUES, X_COORDINATES, Y_COORDINATES, Z_COORDINATES}; + break; + case "common": + metrics = new String[]{BRANCH_CONTRACTION, BRANCH_FRACTAL_DIMENSION, BRANCH_LENGTH, BRANCH_MEAN_RADIUS, // + BRANCH_SURFACE_AREA, BRANCH_VOLUME, COMPLEXITY_INDEX_ACI, COMPLEXITY_INDEX_DCI, CONVEX_HULL_SIZE, // + DEPTH, INNER_LENGTH, INTER_NODE_ANGLE, INTER_NODE_DISTANCE, INTER_NODE_DISTANCE_SQUARED, LENGTH, // + N_BRANCH_POINTS, N_BRANCHES, N_INNER_BRANCHES, N_NODES, N_PRIMARY_BRANCHES, N_SPINES, // + N_TERMINAL_BRANCHES, N_TIPS, NODE_RADIUS, PARTITION_ASYMMETRY, PRIMARY_LENGTH, REMOTE_BIF_ANGLES, // + SHOLL_DECAY, SHOLL_MAX_VALUE, SHOLL_MAX_FITTED, SHOLL_MAX_FITTED_RADIUS, SHOLL_MEAN_VALUE, // + SURFACE_AREA, STRAHLER_NUMBER, TERMINAL_LENGTH, VALUES, VOLUME, X_COORDINATES, Y_COORDINATES, Z_COORDINATES}; + break; + case "quick": + /* NB: This list can only include metrics supported by #getMetricWithoutChecks() */ + metrics = new String[]{ // + LENGTH, MultiTreeStatistics.AVG_BRANCH_LENGTH, N_BRANCH_POINTS, N_TIPS, N_BRANCHES, // + N_PRIMARY_BRANCHES, N_TERMINAL_BRANCHES, // + PATH_MEAN_RADIUS, // + AVG_SPINE_DENSITY, // + STRAHLER_NUMBER, MultiTreeStatistics.HIGHEST_PATH_ORDER, + /* Disabled metrics (likely too specific or uncommon for most users) */ + // MultiTreeStatistics.ASSIGNED_VALUE, MultiTreeStatistics.AVG_CONTRACTION, + // MultiTreeStatistics.AVG_FRACTAL_DIMENSION, + // MultiTreeStatistics.AVG_FRAGMENTATION, + // MultiTreeStatistics.AVG_REMOTE_ANGLE, + // MultiTreeStatistics.AVG_PARTITION_ASYMMETRY, + // PRIMARY_LENGTH, INNER_LENGTH, TERMINAL_LENGTH, + // N_INNER_BRANCHES, + // N_FITTED_PATHS, N_PATHS, N_NODES, + // WIDTH, HEIGHT, DEPTH, + // SHOLL_DECAY, SHOLL_MAX_VALUE, + }; + break; + default: + throw new IllegalArgumentException("Unrecognized type"); + } + return Arrays.stream(metrics).collect(Collectors.toList()); + } + + /** + * Gets the list of most commonly used metrics. + * + * @return the list of commonly used metrics. + * @see #getMetrics(String) + */ + @Deprecated + public static List getMetrics() { + return getMetrics("common"); + } + + protected static Map getAnnotatedLength(final DirectedWeightedGraph graph, final int level, final char lr, final boolean norm) { + final NodeStatistics nodeStats = new NodeStatistics<>(graph.vertexSet(lr)); + final Map> annotatedNodesMap = nodeStats.getAnnotatedNodes(level); + final HashMap lengthMap = new HashMap<>(); + for (final Map.Entry> entry : annotatedNodesMap.entrySet()) { + final BrainAnnotation annotation = entry.getKey(); + final Set nodeSubset = entry.getValue(); + final DirectedWeightedSubgraph subgraph = graph.getSubgraph(nodeSubset); + final double subgraphWeight = subgraph.sumEdgeWeights(true); + lengthMap.put(annotation, subgraphWeight); + } + if (norm) { + final double sumLength = graph.sumEdgeWeights(); + lengthMap.replaceAll((k, v) -> v / sumLength); + } + return lengthMap; + } + + protected static Map getAnnotatedLengthsByHemisphere(final DirectedWeightedGraph graph, final int level, final boolean norm) { + final char ipsiFlag = graph.getRoot().getHemisphere(); + if (ipsiFlag == BrainAnnotation.ANY_HEMISPHERE) + throw new IllegalArgumentException("Tree's root has its hemisphere flag unset"); + final char contraFlag = (ipsiFlag == BrainAnnotation.LEFT_HEMISPHERE) ? BrainAnnotation.RIGHT_HEMISPHERE : BrainAnnotation.LEFT_HEMISPHERE; + final Map ipsiMap = getAnnotatedLength(graph, level, ipsiFlag, norm); + final Map contraMap = getAnnotatedLength(graph, level, contraFlag, norm); + final Map finalMap = new HashMap<>(); + ipsiMap.forEach((k, ipsiLength) -> { + double[] values = new double[2]; + final Double contraLength = contraMap.get(k); + values[0] = ipsiLength; + values[1] = (contraLength == null) ? 0d : contraLength; + finalMap.put(k, values); + }); + contraMap.keySet().removeIf(k -> ipsiMap.get(k) != null); + contraMap.forEach((k, contraLength) -> finalMap.put(k, new double[]{0d, contraLength})); + return finalMap; + } + + /** + * Creates a TreeStatistics instance from a group of Trees and a specific metric for convenient retrieval + * of histograms + * @param trees the collection of trees + * @param metric the measurement + * @return the TreeStatistics instance + */ + public static TreeStatistics fromCollection(final Collection trees, final String metric) { + final Iterator iterator = trees.iterator(); + final TreeStatistics holder = new TreeStatistics(iterator.next()); + if (trees.size() == 1) return holder; + holder.tree.setLabel(getLabelFromTreeCollection(trees)); + final String normMeasurement = getNormalizedMeasurement(metric); + final DescriptiveStatistics holderStats = holder.getDescriptiveStats(normMeasurement); + while (iterator.hasNext()) { + final Tree tree = iterator.next(); + final TreeStatistics treeStats = new TreeStatistics(tree); + final DescriptiveStatistics dStats = treeStats.getDescriptiveStats(normMeasurement); + for (final double v : dStats.getValues()) { + holderStats.addValue(v); + } + } + return holder; + } + + private static String getLabelFromTreeCollection(final Collection trees) { + final StringBuilder sb = new StringBuilder(); + for (final Tree tree : trees) { + if (tree.getLabel() != null) sb.append(tree.getLabel()).append(" "); + } + return (sb.length() == 0) ? "Grouped Cells" : sb.toString().trim(); + } + + protected static String tryReallyHardToGuessMetric(final String guess) { + final String normGuess = guess.toLowerCase(); + if (normGuess.contains("contrac")) { + return (normGuess.contains("path")) ? PATH_CONTRACTION : BRANCH_CONTRACTION; + } + if (normGuess.contains("remote") && normGuess.contains("angle")) { + return REMOTE_BIF_ANGLES; + } + if (normGuess.contains("partition") && normGuess.contains("asymmetry")) { + return PARTITION_ASYMMETRY; + } + if (normGuess.contains("fractal")) { + return (normGuess.contains("path")) ? PATH_FRACTAL_DIMENSION : BRANCH_FRACTAL_DIMENSION; + } + if (normGuess.contains("length") || normGuess.contains("cable")) { + if (normGuess.contains("term")) { + return TERMINAL_LENGTH; + } else if (normGuess.contains("prim")) { + return PRIMARY_LENGTH; + } else if (normGuess.contains("inner")) { + return INNER_LENGTH; + } else if (normGuess.contains("path")) { + return PATH_LENGTH; + } else { + return BRANCH_LENGTH; + } + } + if (normGuess.contains("angle")) { + if (normGuess.contains("path") && normGuess.contains("ext")) { + if (normGuess.contains("xz")) + return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_XZ : PATH_EXT_ANGLE_XZ; + else if (normGuess.contains("zy")) + return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_ZY : PATH_EXT_ANGLE_ZY; + else return (normGuess.contains("rel")) ? PATH_EXT_ANGLE_REL_XY : PATH_EXT_ANGLE_XY; + } else if (normGuess.contains("term")) { + if (normGuess.contains("xz")) return TERMINAL_EXTENSION_ANGLE_XZ; + else if (normGuess.contains("zy")) return TERMINAL_EXTENSION_ANGLE_ZY; + else return TERMINAL_EXTENSION_ANGLE_XY; + } else if (normGuess.contains("prim")) { + if (normGuess.contains("xz")) return PRIMARY_EXTENSION_ANGLE_XZ; + else if (normGuess.contains("zy")) return PRIMARY_EXTENSION_ANGLE_ZY; + else return PRIMARY_EXTENSION_ANGLE_XY; + } else if (normGuess.contains("inner")) { + if (normGuess.contains("xz")) return INNER_EXTENSION_ANGLE_XZ; + else if (normGuess.contains("zy")) return INNER_EXTENSION_ANGLE_ZY; + else return INNER_EXTENSION_ANGLE_XY; + } else if (normGuess.contains("xz")) return BRANCH_EXTENSION_ANGLE_XZ; + else if (normGuess.contains("zy")) return BRANCH_EXTENSION_ANGLE_ZY; + else return BRANCH_EXTENSION_ANGLE_XY; + } + if (normGuess.contains("path") && normGuess.contains("order")) { + return PATH_ORDER; + } + if (normGuess.contains("bp") || normGuess.contains("branch points") || normGuess.contains("junctions")) { + return N_BRANCH_POINTS; + } + if (normGuess.contains("nodes")) { + return N_NODES; + } + if (normGuess.contains("node") && (normGuess.contains("dis") || normGuess.contains("dx"))) { + if (normGuess.contains("sq")) { + return INTER_NODE_DISTANCE_SQUARED; + } else if (normGuess.contains("angle")) { + return INTER_NODE_ANGLE; + } else { + return INTER_NODE_DISTANCE; + } + } + if (normGuess.contains("radi")) { + if (normGuess.contains("mean") || normGuess.contains("avg") || normGuess.contains("average")) { + return (normGuess.contains("path")) ? PATH_MEAN_RADIUS : BRANCH_MEAN_RADIUS; + } else { + return NODE_RADIUS; + } + } + if (normGuess.contains("spines") || normGuess.contains("varicosities")) { + if (normGuess.contains("dens") || normGuess.contains("path")) { + return PATH_SPINE_DENSITY; + } else { + return N_SPINES; + } + } + if (normGuess.contains("values") || normGuess.contains("intensit")) { + return VALUES; + } + if (normGuess.matches(".*\\bx\\b.*")) { + return X_COORDINATES; + } + if (normGuess.matches(".*\\by\\b.*")) { + return Y_COORDINATES; + } + if (normGuess.matches(".*\\bz\\b.*")) { + return Z_COORDINATES; + } + return "unknown"; + } + + protected static String getNormalizedMeasurement(final String measurement) { + if (isExactMetricMatch()) return measurement; + for (final String flag : ALL_FLAGS) { + if (flag.equalsIgnoreCase(measurement)) return flag; + } + for (final String flag : MultiTreeStatistics.getMetrics()) { + if (flag.equalsIgnoreCase(measurement)) return flag; + } + for (final String flag : getMetrics("deprecated")) { + if (flag.equalsIgnoreCase(measurement)) return flag; + } + final String normMeasurement = tryReallyHardToGuessMetric(measurement); + if (!measurement.equals(normMeasurement)) { + SNTUtils.log("\"" + normMeasurement + "\" assumed"); + if ("unknown".equals(normMeasurement)) { + throw new UnknownMetricException("Unrecognizable measurement! " + "Maybe you meant one of the following?: " + Arrays.toString(ALL_FLAGS)); + } + } + return normMeasurement; + } + + /** + * Checks if 'fuzzy matching' of metrics is active. + * @return true if exact string matching is active, false if approximate matching is active + */ + public static boolean isExactMetricMatch() { + return exactMetricMatch; + } + + /** + * Sets whether approximate string matching ('fuzzy matching') should be used when retrieving metrics. + * @param exactMetricMatch exact string matching is used if true, approximate if false + */ + public static void setExactMetricMatch(final boolean exactMetricMatch) { + TreeStatistics.exactMetricMatch = exactMetricMatch; + } + + /** + * Computes the specified metric. + * + * @param metric the single-value metric to be computed (case-insensitive). While it is expected to be an element + * of {@link #getAllMetrics()}, if {@link #isExactMetricMatch()} is set, {@code metric} can be + * specified in a loose manner: If {@code metric} is not initially recognized, a heuristic will match + * it to the closest entry in the list of possible metrics. E.g., "# bps", "n junctions", will be both + * mapped to {@link #N_BRANCH_POINTS}. Details on the matching are printed to the Console when in + * debug mode. + * @return the computed value (average if metric is associated with multiple values) + * @throws UnknownMetricException if metric is not recognized + * @see #getMetrics() + * @see #setExactMetricMatch(boolean) + */ + public Number getMetric(final String metric) throws UnknownMetricException { + return getMetricWithoutChecks(getNormalizedMeasurement(metric)); + } + + protected Number getMetricWithoutChecks(final String metric) throws UnknownMetricException { + switch (metric) { + case MultiTreeStatistics.ASSIGNED_VALUE: + return tree.getAssignedValue(); + case MultiTreeStatistics.AVG_BRANCH_LENGTH: + return getAvgBranchLength(); + case MultiTreeStatistics.AVG_CONTRACTION: + case BRANCH_CONTRACTION: + return getAvgContraction(); + case MultiTreeStatistics.AVG_FRAGMENTATION: + return getAvgFragmentation(); + case MultiTreeStatistics.AVG_REMOTE_ANGLE: + return getAvgRemoteBifAngle(); + case MultiTreeStatistics.AVG_PARTITION_ASYMMETRY: + return getAvgPartitionAsymmetry(); + case MultiTreeStatistics.AVG_FRACTAL_DIMENSION: + return getAvgFractalDimension(); + case MultiTreeStatistics.PRIMARY_LENGTH: + case PRIMARY_LENGTH: + return getPrimaryLength(); + case MultiTreeStatistics.TERMINAL_LENGTH: + case TERMINAL_LENGTH: + return getTerminalLength(); + case MultiTreeStatistics.INNER_LENGTH: + case INNER_LENGTH: + return getInnerLength(); + case MultiTreeStatistics.HIGHEST_PATH_ORDER: + return getHighestPathOrder(); + case AVG_SPINE_DENSITY: + case PATH_SPINE_DENSITY: + return getSpineOrVaricosityDensity(); + case DEPTH: + return getDepth(); + case HEIGHT: + return getHeight(); + case LENGTH: + return getCableLength(); + case N_BRANCH_POINTS: + return getBranchPoints().size(); + case N_BRANCHES: + return getNBranches(); + case N_FITTED_PATHS: + return getNFittedPaths(); + case N_NODES: + return getNNodes(); + case N_PATHS: + return getNPaths(); + case N_PRIMARY_BRANCHES: + return getPrimaryBranches().size(); + case N_INNER_BRANCHES: + return getInnerBranches().size(); + case N_TERMINAL_BRANCHES: + return getTerminalBranches().size(); + case N_TIPS: + return getTips().size(); + case N_SPINES: + return getNoSpinesOrVaricosities(); + case STRAHLER_NUMBER: + return getStrahlerNumber(); + case STRAHLER_RATIO: + return getStrahlerBifurcationRatio(); + case WIDTH: + return getWidth(); + default: + if (metric.startsWith("Sholl: ")) + return getShollMetric(metric); + return new TreeStatistics(tree).getSummaryStats(metric).getMean(); + } + } + + /** + * Restricts analysis to Paths sharing the specified SWC flag(s). + * + * @param types the allowed SWC flags (e.g., {@link Path#SWC_AXON}, etc.) + */ + public void restrictToSWCType(final int... types) { + initializeSnapshotTree(); + tree = tree.subTree(types); + } + + private void initializeSnapshotTree() { + if (unfilteredTree == null) { + unfilteredTree = new Tree(tree.list()); + unfilteredTree.setLabel(tree.getLabel()); + } + sAnalyzer = null; // reset Strahler analyzer + } + + /** + * Removes any filtering restrictions that may have been set. Once called, + * subsequent analysis will use all paths initially parsed by the constructor. + * Does nothing if no paths are currently being excluded from the analysis. + */ + public void resetRestrictions() { + if (unfilteredTree == null) return; // no filtering has occurred + tree.replaceAll(unfilteredTree.list()); + joints = null; + primaryBranches = null; + innerBranches = null; + terminalBranches = null; + tips = null; + sAnalyzer = null; + shllAnalyzer = null; + fittedPathsCounter = unfilteredPathsFittedPathsCounter; + convexAnalyzer = null; + } + + private void updateFittedPathsCounter(final Path filteredPath) { + if (fittedPathsCounter > 0 && filteredPath.isFittedVersionOfAnotherPath()) fittedPathsCounter--; + } + + /** + * Returns the set of parsed Paths. + * + * @return the set of paths currently being considered for analysis. + * @see #resetRestrictions() + */ + public Tree getParsedTree() { + return tree; + } + + /** + * Outputs a summary of the current analysis to the measurements table using the + * default Tree label. + * + * @param groupByType if true measurements are grouped by SWC-type flag + * @see #setTable(DefaultGenericTable) + */ + public void summarize(final boolean groupByType) { + summarize(tree.getLabel(), groupByType); + } + + /** + * Outputs a summary of the current analysis to the measurements table. + * + * @param rowHeader the String to be used as label for the summary + * @param groupByType if true measurements are grouped by SWC-type flag + * @see #run() + * @see #setTable(DefaultGenericTable) + */ + public void summarize(final String rowHeader, final boolean groupByType) { + measure(rowHeader, TreeStatistics.getMetrics("quick"), groupByType); + } + + protected int getNextRow(final String rowHeader) { + table.appendRow((rowHeader == null) ? "" : rowHeader); + return table.getRowCount() - 1; + } + + protected String getSWCTypesAsString() { + final StringBuilder sb = new StringBuilder(); + final Set types = tree.getSWCTypes(true); + for (int type : types) { + sb.append(Path.getSWCtypeName(type, true)).append(" "); + } + return sb.toString().trim(); + } + + /** + * Measures this Tree, outputting the result to the measurements table using + * default row labels. If a Context has been specified, the table is updated. + * Otherwise, table contents are printed to Console. + * + * @param metrics the list of metrics to be computed. When null or an empty + * collection is specified, {@link #getMetrics()} is used. + * @param groupByType if false, metrics are computed to all branches in the + * Tree. If true, measurements will be split by SWC type + * annotations (axon, dendrite, etc.) + * @see #setExactMetricMatch(boolean) + */ + public void measure(Collection metrics, boolean groupByType) { + measure(tree.getLabel(), metrics, groupByType); + } + + /** + * Measures this Tree, outputting the result to the measurements table. If a + * Context has been specified, the table is updated. Otherwise, table contents + * are printed to Console. + * + * @param rowHeader the row header label + * @param metrics the list of metrics to be computed. When null or an empty + * collection is specified, {@link #getMetrics()} is used. + * @param groupByType if false, metrics are computed to all branches in the + * Tree. If true, measurements will be split by SWC type + * annotations (axon, dendrite, etc.) + * @see #setExactMetricMatch(boolean) + */ + public void measure(final String rowHeader, final Collection metrics, final boolean groupByType) { + if (table == null) table = new SNTTable(); + final Collection measuringMetrics = (metrics == null || metrics.isEmpty()) ? getMetrics() : metrics; + for (final String metric : measuringMetrics) { + final String unit = getUnit(metric); + final String metricHeader = (unit.length() > 1) ? metric + " (" + unit + ")" : metric; + if (groupByType) { + for (final int type : tree.getSWCTypes(false)) { + restrictToSWCType(type); + logStatsToTable(metric, metricHeader, rowHeader); + resetRestrictions(); + } + } else { + logStatsToTable(metric, metricHeader, rowHeader); + } + } + updateAndDisplayTable(); + } + + private void logStatsToTable(final String metric, final String metricHeader, final String rowHeader) { + SummaryStatistics summaryStatistics; + try { + summaryStatistics = getSummaryStats(metric); + } catch (final IllegalArgumentException | IndexOutOfBoundsException | NullPointerException e) { + // e.g. Node values cannot be assigned or a convex hull of a single-path tree + summaryStatistics = new SummaryStatistics(); + summaryStatistics.addValue(Double.NaN); + SNTUtils.log(e.getMessage()); + } + if (summaryStatistics.getN() == 1) { + ((SNTTable) table).set(metricHeader + " [Single value]", rowHeader, summaryStatistics.getSum()); + } else { + ((SNTTable) table).set(metricHeader + " [MIN]", rowHeader, summaryStatistics.getMin()); + ((SNTTable) table).set(metricHeader + " [MAX]", rowHeader, summaryStatistics.getMax()); + ((SNTTable) table).set(metricHeader + " [MEAN]", rowHeader, summaryStatistics.getMean()); + ((SNTTable) table).set(metricHeader + " [STD_DEV]", rowHeader, summaryStatistics.getStandardDeviation()); + ((SNTTable) table).set(metricHeader + " [N]", rowHeader, summaryStatistics.getN()); + } + ((SNTTable) table).set("SWC Type(s)", rowHeader, getSWCTypesAsString()); + } + + /** + * Computes the {@link SummaryStatistics} for the specified measurement. + * + * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) + * @return the SummaryStatistics object. + */ + public SummaryStatistics getSummaryStats(final String metric) { + final SummaryStatistics sStats = new SummaryStatistics(); + assembleStats(new StatisticsInstance(sStats), getNormalizedMeasurement(metric)); + return sStats; + } + + /** + * Computes the {@link DescriptiveStatistics} for the specified measurement. + * + * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) + * @return the DescriptiveStatistics object. + */ + public DescriptiveStatistics getDescriptiveStats(final String metric) { + final String normMeasurement = getNormalizedMeasurement(metric); + if (!lastDstatsCanBeRecycled(normMeasurement)) { + final DescriptiveStatistics dStats = new DescriptiveStatistics(); + assembleStats(new StatisticsInstance(dStats), normMeasurement); + lastDstats = new LastDstats(normMeasurement, dStats); + } + return lastDstats.dStats; + } + + /** + * Sets the measurements table. + * + * @param table the table to be used by this TreeStatistics instance + * @param title the title of the table display window + */ + public void setTable(final DefaultGenericTable table, final String title) { + this.table = table; + this.tableTitle = title; + } + + /** + * Gets the table currently being used by this TreeStatistics instance + * + * @return the table + */ + public DefaultGenericTable getTable() { + return table; + } + + /** + * Sets the table for this TreeStatistics instance. + * + * @param table the table to be used by this TreeStatistics instance + * @see #summarize(boolean) + */ + public void setTable(final DefaultGenericTable table) { + this.table = table; + } + + /** + * Retrieves the amount of cable length present on each brain compartment + * innervated by the analyzed neuron. + * + * @param level the ontological depth of the compartments to be considered + * @return the map containing the brain compartments as keys, and cable lengths + * as values. + * @see AllenCompartment#getOntologyDepth() + */ + public Map getAnnotatedLength(final int level) { + return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.ANY_HEMISPHERE, false); + } + + /** + * Retrieves the amount of cable length present on each brain compartment + * innervated by the analyzed neuron. + * + * @param level the ontological depth of the compartments to be considered + * @param hemisphere typically 'left' or 'right'. The hemisphere flag ( + * {@link BrainAnnotation#LEFT_HEMISPHERE} or + * {@link BrainAnnotation#RIGHT_HEMISPHERE}) is extracted from + * the first character of the string (case-insensitive). + * Ignored if not a recognized option + * @return the map containing the brain compartments as keys, and cable lengths + * as values. + * @see AllenCompartment#getOntologyDepth() + */ + public Map getAnnotatedLength(final int level, final String hemisphere) { + return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.getHemisphereFlag(hemisphere), false); + } + + /** + * Retrieves the amount of cable length present on each brain compartment + * innervated by the analyzed neuron. + * + * @param level the ontological depth of the compartments to be considered + * @param hemisphere typically 'left' or 'right'. The hemisphere flag ( + * {@link BrainAnnotation#LEFT_HEMISPHERE} or + * {@link BrainAnnotation#RIGHT_HEMISPHERE}) is extracted from + * the first character of the string (case-insensitive). + * Ignored if not a recognized option + * @param norm whether length should be normalized to the cells' cable + * length + * @return the map containing the brain compartments as keys, and cable lengths + * as values. + * @see AllenCompartment#getOntologyDepth() + */ + public Map getAnnotatedLength(final int level, final String hemisphere, final boolean norm) { + return getAnnotatedLength(tree.getGraph(), level, BrainAnnotation.getHemisphereFlag(hemisphere), norm); + } + + /** + * Retrieves the amount of cable length present on each brain compartment innervated by the analyzed neuron + * in the two brain hemispheres. Lengths are absolute and not normalized to the cells' cable length. + * + * @param level the ontological depth of the compartments to be considered + * @return the map containing the brain compartments as keys, and cable lengths per hemisphere as values. + * @see AllenCompartment#getOntologyDepth() + */ + public Map getAnnotatedLengthsByHemisphere(final int level) { + return getAnnotatedLengthsByHemisphere(tree.getGraph(), level, false); + } + + /** + * Retrieves the of cable length frequencies across brain areas. + * + * @return the histogram of cable length frequencies. + */ + public SNTChart getAnnotatedLengthHistogram() { + return getAnnotatedLengthHistogram(Integer.MAX_VALUE); + } + + /** + * Retrieves the histogram of cable length frequencies across brain areas of the + * specified ontology level. + * + * @param depth the ontological depth of the compartments to be considered + * @return the annotated length histogram + * @see AllenCompartment#getOntologyDepth() + */ + public SNTChart getAnnotatedLengthHistogram(final int depth) { + final Map map = getAnnotatedLength(depth); + return getAnnotatedLengthHistogram(map, depth, ""); + } + + /** + * Retrieves the histogram of cable length frequencies across brain areas of the + * specified ontology level across the specified hemisphere. + * + * @param depth the ontological depth of the compartments to be considered + * @param hemisphere 'left', 'right' or 'ratio' (case-insensitive). Ignored if + * not a recognized option + * @return the annotated length histogram + * @see AllenCompartment#getOntologyDepth() + */ + public SNTChart getAnnotatedLengthHistogram(int depth, String hemisphere) { + if ("ratio".equalsIgnoreCase(hemisphere.trim())) return getAnnotatedLengthsByHemisphereHistogram(depth); + final Map map = getAnnotatedLength(depth, hemisphere); + + String label; + final char hemiFlag = BrainAnnotation.getHemisphereFlag(hemisphere); + switch (hemiFlag) { + case BrainAnnotation.LEFT_HEMISPHERE: + label = "Left hemi."; + break; + case BrainAnnotation.RIGHT_HEMISPHERE: + label = "Right hemi."; + break; + default: + label = ""; + } + return getAnnotatedLengthHistogram(map, depth, label); + } + + protected SNTChart getAnnotatedLengthsByHemisphereHistogram(int depth) { + final DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + Map seriesMap = getAnnotatedLengthsByHemisphere(depth); + Map undefinedLengthMap = new HashMap<>(); + seriesMap.entrySet().stream().sorted((e1, e2) -> -Double.compare(e1.getValue()[0], e2.getValue()[0])).forEach(entry -> { + if (entry.getKey() == null || entry.getKey().getOntologyDepth() == 0) { + // a null brain annotation or the root brain itself + undefinedLengthMap.put(entry.getKey(), entry.getValue()); + } else { + dataset.addValue(entry.getValue()[0], "Ipsilateral", entry.getKey().acronym()); + dataset.addValue(entry.getValue()[1], "Contralateral", entry.getKey().acronym()); + } + }); + + if (!undefinedLengthMap.isEmpty()) { + undefinedLengthMap.forEach((k, v) -> { + dataset.addValue(v[0], "Ipsilateral", BrainAnnotation.simplifiedString(k)); + dataset.addValue(v[1], "Contralateral", BrainAnnotation.simplifiedString(k)); + }); + } + final String axisTitle = (depth == Integer.MAX_VALUE) ? "no filtering" : "depth ≤" + depth; + final JFreeChart chart = AnalysisUtils.createCategoryPlot( // + "Brain areas (N=" + (seriesMap.size() - undefinedLengthMap.size()) + ", " + axisTitle + ")", // domain axis title + String.format("Cable length (%s)", getUnit()), // range axis title + "", // axis unit (already applied) + dataset, 2); + final String tLabel = (tree.getLabel() == null) ? "" : tree.getLabel(); + return new SNTChart(tLabel + " Annotated Length", chart, new Dimension(400, 600)); + } + + protected SNTChart getAnnotatedLengthHistogram(final Map map, final int depth, final String secondaryLabel) { + final DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + Map undefinedLengthMap = new HashMap<>(); + final String seriesLabel = (depth == Integer.MAX_VALUE) ? "no filtering" : "depth ≤" + depth; + map.entrySet().stream().sorted((e1, e2) -> -e1.getValue().compareTo(e2.getValue())).forEach(entry -> { + if (entry.getKey() == null || entry.getKey().getOntologyDepth() == 0) { + // a null brain annotation or the root brain itself + undefinedLengthMap.put(entry.getKey(), entry.getValue()); + } else dataset.addValue(entry.getValue(), seriesLabel, entry.getKey().acronym()); + }); + if (!undefinedLengthMap.isEmpty()) { + undefinedLengthMap.forEach((k, v) -> dataset.addValue(v, seriesLabel, BrainAnnotation.simplifiedString(k))); + } + final JFreeChart chart = AnalysisUtils.createCategoryPlot( // + "Brain areas (N=" + (map.size() - undefinedLengthMap.size()) + ", " + seriesLabel + ")", // domain axis title + String.format("Cable length (%s)", getUnit()), // range axis title + "", // unit: already specified + dataset); + final String tLabel = (tree.getLabel() == null) ? "" : tree.getLabel(); + final SNTChart frame = new SNTChart(tLabel + " Annotated Length", chart, new Dimension(400, 600)); + if (secondaryLabel != null) frame.annotate(secondaryLabel); + return frame; + } + + /** + * Retrieves the histogram of relative frequencies histogram for a univariate + * measurement. The number of bins is determined using the Freedman-Diaconis + * rule. + * + * @param metric the measurement ({@link #N_NODES}, {@link #NODE_RADIUS}, etc.) + * @return the frame holding the histogram + */ + public SNTChart getHistogram(final String metric) { + final String normMeasurement = getNormalizedMeasurement(metric); + final HistogramDatasetPlus datasetPlus = new HDPlus(normMeasurement, true); + return getHistogram(normMeasurement, datasetPlus); + } + + public SNTChart getPolarHistogram(final String metric) { + final String normMeasurement = getNormalizedMeasurement(metric); + final HistogramDatasetPlus datasetPlus = new HDPlus(normMeasurement, true); + final JFreeChart chart = AnalysisUtils.createPolarHistogram(normMeasurement, getUnit(), lastDstats.dStats, datasetPlus); + return new SNTChart("Polar Hist. " + tree.getLabel(), chart); + } + + /** + * Assembles a Flow plot (aka Sankey diagram) for the specified feature using + * "mean" as integration statistic, and no cutoff value. + * + * @see #getFlowPlot(String, Collection, String, double, boolean) + */ + public SNTChart getFlowPlot(final String feature, final Collection annotations, final boolean normalize) { + return getFlowPlot(feature, annotations, "sum", Double.MIN_VALUE, normalize); + } + + /** + * Assembles a Flow plot (aka Sankey diagram) for the specified feature using + * "mean" as integration statistic, no cutoff value, and all of the brain + * regions of the specified ontology depth. + * + * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, + * {@value MultiTreeStatistics#N_BRANCH_POINTS}, + * {@value MultiTreeStatistics#N_TIPS}, etc.). + * @param depth the ontological depth of the compartments to be considered + * @return the flow plot + * @see #getFlowPlot(String, Collection, String, double, boolean) + */ + public SNTChart getFlowPlot(final String feature, final int depth) { + return getFlowPlot(feature, depth, Double.MIN_VALUE, true); + } + + /** + * Assembles a Flow plot (aka Sankey diagram) for the specified feature using + * "mean" as integration statistic, no cutoff value, and all of the brain + * regions of the specified ontology depth. * + * + * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, + * {@value MultiTreeStatistics#N_BRANCH_POINTS}, + * {@value MultiTreeStatistics#N_TIPS}, etc.) + * @param depth the ontological depth of the compartments to be considered + * @param cutoff a filtering option. If the computed {@code feature} for an + * annotation is below this value, that annotation is excluded + * from the plot * @param normalize If true, values are retrieved + * as ratios. E.g., If {@code feature} is + * {@value MultiTreeStatistics#LENGTH}, and {@code cutoff} 0.1, + * BrainAnnotations in {@code annotations} associated with less + * than 10% of cable length are ignored. + * @return the flow plot + */ + public SNTChart getFlowPlot(final String feature, final int depth, final double cutoff, final boolean normalize) { + return getFlowPlot(feature, getAnnotations(depth), "sum", cutoff, normalize); + } + + /** + * Assembles a Flow plot (aka Sankey diagram) for the specified feature using + * "mean" as integration statistic, and no cutoff value. + * + * @see #getFlowPlot(String, Collection, String, double, boolean) + */ + public SNTChart getFlowPlot(final String feature, final Collection annotations) { + return getFlowPlot(feature, annotations, "sum", Double.MIN_VALUE, false); + } + + /** + * Assembles a Flow plot (aka Sankey diagram) for the specified feature. + * + * @param feature the feature ({@value MultiTreeStatistics#LENGTH}, + * {@value MultiTreeStatistics#N_BRANCH_POINTS}, + * {@value MultiTreeStatistics#N_TIPS}, etc.). Note that the + * majority of {@link MultiTreeStatistics#getAllMetrics()} + * metrics are currently not supported. + * @param annotations the BrainAnnotations to be queried. Null not allowed. + * @param statistic the integration statistic (lower case). Either "mean", + * "sum", "min" or "max". Null not allowed. + * @param cutoff a filtering option. If the computed {@code feature} for an + * annotation is below this value, that annotation is + * excluded from the plot + * @param normalize If true, values are retrieved as ratios. E.g., If + * {@code feature} is {@value MultiTreeStatistics#LENGTH}, + * and {@code cutoff} 0.1, BrainAnnotations in + * {@code annotations} associated with less than 10% of cable + * length are ignored. + * @return the SNTChart holding the flow plot + */ + public SNTChart getFlowPlot(final String feature, final Collection annotations, final String statistic, final double cutoff, final boolean normalize) { + final GroupedTreeStatistics gts = new GroupedTreeStatistics(); + gts.addGroup(Collections.singleton(getParsedTree()), (null == tree.getLabel()) ? "" : tree.getLabel()); + final SNTChart chart = gts.getFlowPlot(feature, annotations, statistic, cutoff, normalize); + chart.setTitle("Flow Plot [Single Cell]"); + return chart; + } + + protected SNTChart getHistogram(final String normMeasurement, final HistogramDatasetPlus datasetPlus) { + final JFreeChart chart = AnalysisUtils.createHistogram(normMeasurement, getUnit(), lastDstats.dStats, datasetPlus); + return new SNTChart("Hist. " + tree.getLabel(), chart); + } + + protected void assembleStats(final StatisticsInstance stat, final String measurement) { + final String m = getNormalizedMeasurement(measurement); + switch (m) { + case BRANCH_CONTRACTION: + try { + for (final Path p : getBranches()) + stat.addValue(p.getContraction()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case BRANCH_EXTENSION_ANGLE_XY: + try { + getBranches().forEach(b -> stat.addValue(b.getExtensionAngleXY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case BRANCH_EXTENSION_ANGLE_XZ: + try { + getBranches().forEach(b -> stat.addValue(b.getExtensionAngleXZ(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case BRANCH_EXTENSION_ANGLE_ZY: + try { + getBranches().forEach(b -> stat.addValue(b.getExtensionAngleZY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case BRANCH_FRACTAL_DIMENSION: + try { + getFractalDimension().forEach(stat::addValue); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case BRANCH_LENGTH: + try { + for (final Path p : getBranches()) + stat.addValue(p.getLength()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case BRANCH_MEAN_RADIUS: + try { + for (final Path p : getBranches()) + stat.addValue(p.getMeanRadius()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case BRANCH_SURFACE_AREA: + try { + for (final Path p : getBranches()) + stat.addValue(p.getApproximatedSurface()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case BRANCH_VOLUME: + try { + for (final Path p : getBranches()) + stat.addValue(p.getApproximatedVolume()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case COMPLEXITY_INDEX_ACI: + // implementation: doi: 10.1523/JNEUROSCI.4434-06.2007 + double sumPathOrders = 0; + for (final Path p : tree.list()) + sumPathOrders += p.getOrder() - 1; + stat.addValue(sumPathOrders / tree.list().size()); + break; + case COMPLEXITY_INDEX_DCI: + try { + // Implementation by chronological order: + // www.jneurosci.org/content/19/22/9928#F6 + // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3373517/ + // https://journals.physiology.org/doi/full/10.1152/jn.00829.2011 + final DirectedWeightedGraph graph = tree.getGraph(); + final List graphTips = graph.getTips(); + final SWCPoint root = graph.getRoot(); + double sumBranchTipOrders = 0; + for (final SWCPoint tip : graphTips) { + for (final SWCPoint vx : graph.getShortestPathVertices(root, tip)) { + if (graph.outDegreeOf(vx) > 1) sumBranchTipOrders++; + } + } + stat.addValue((sumBranchTipOrders + graphTips.size()) * getCableLength() / getPrimaryBranches().size()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case CONVEX_HULL_BOUNDARY_SIZE: + case CONVEX_HULL_BOXIVITY: + case CONVEX_HULL_ELONGATION: + case CONVEX_HULL_ROUNDNESS: + case CONVEX_HULL_SIZE: + stat.addValue(getConvexHullMetric(m)); + break; + case CONVEX_HULL_CENTROID_ROOT_DISTANCE: + final PointInImage root = tree.getRoot(); + if (root == null) stat.addValue(Double.NaN); + else stat.addValue(getConvexAnalyzer().getCentroid().distanceTo(root)); + break; + case DEPTH: + stat.addValue(getDepth()); + break; + case GRAPH_DIAMETER: + try { + stat.addValue(tree.getGraph().getLongestPath(true).getLength()); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case GRAPH_DIAMETER_ANGLE_XY: + try { + stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleXY(false)); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case GRAPH_DIAMETER_ANGLE_XZ: + try { + stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleXZ(false)); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case GRAPH_DIAMETER_ANGLE_ZY: + try { + stat.addValue(tree.getGraph().getLongestPath(true).getExtensionAngleZY(false)); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case HEIGHT: + stat.addValue(getHeight()); + break; + case INNER_EXTENSION_ANGLE_XY: + try { + getInnerBranches().forEach(b -> stat.addValue(b.getExtensionAngleXY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case INNER_EXTENSION_ANGLE_XZ: + try { + getInnerBranches().forEach(b -> stat.addValue(b.getExtensionAngleXZ(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case INNER_EXTENSION_ANGLE_ZY: + try { + getInnerBranches().forEach(b -> stat.addValue(b.getExtensionAngleZY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case INNER_LENGTH: + try { + getInnerBranches().forEach(b -> stat.addValue(b.getLength())); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case INTER_NODE_ANGLE: + for (final Path p : tree.list()) { + if (p.size() < 3) continue; + for (int i = 2; i < p.size(); i++) { + stat.addValue(p.getAngle(i)); + } + } + break; + case INTER_NODE_DISTANCE: + for (final Path p : tree.list()) { + if (p.size() < 2) continue; + for (int i = 1; i < p.size(); i += 1) { + stat.addValue(p.getNode(i).distanceTo(p.getNode(i - 1))); + } + } + break; + case INTER_NODE_DISTANCE_SQUARED: + for (final Path p : tree.list()) { + if (p.size() < 2) continue; + for (int i = 1; i < p.size(); i += 1) { + stat.addValue(p.getNode(i).distanceSquaredTo(p.getNode(i - 1))); + } + } + break; + case LENGTH: + stat.addValue(getCableLength()); + break; + case N_BRANCH_NODES: + try { + getBranches().forEach(b -> stat.addValue(b.size())); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case N_BRANCH_POINTS: + stat.addValue(getBranchPoints().size()); + break; + case N_BRANCHES: + stat.addValue(getNBranches()); + break; + case N_FITTED_PATHS: + stat.addValue(getNFittedPaths()); + break; + case N_INNER_BRANCHES: + stat.addValue(getInnerBranches().size()); + break; + case N_NODES: + stat.addValue(getNNodes()); + break; + case N_PATH_NODES: + tree.list().forEach(path -> stat.addValue(path.size())); + break; + case N_PATHS: + stat.addValue(getNPaths()); + break; + case N_PRIMARY_BRANCHES: + stat.addValue(getPrimaryBranches().size()); + break; + case N_SPINES: + stat.addValue(getNoSpinesOrVaricosities()); + break; + case N_TERMINAL_BRANCHES: + stat.addValue(getTerminalBranches().size()); + break; + case N_TIPS: + stat.addValue(getTips().size()); + break; + case NODE_RADIUS: + for (final Path p : tree.list()) { + for (int i = 0; i < p.size(); i++) { + stat.addValue(p.getNodeRadius(i)); + } + } + break; + case PARTITION_ASYMMETRY: + try { + for (final double asymmetry : getPartitionAsymmetry()) + stat.addValue(asymmetry); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case PATH_CHANNEL: + for (final Path p : tree.list()) { + stat.addValue(p.getChannel()); + } + break; + case PATH_CONTRACTION: + try { + for (final Path p : tree.list()) + stat.addValue(p.getContraction()); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case PATH_EXT_ANGLE_XY: + case PATH_EXT_ANGLE_REL_XY: + for (final Path p : tree.list()) + stat.addValue(p.getExtensionAngleXY(PATH_EXT_ANGLE_REL_XY.equals(m))); + break; + case PATH_EXT_ANGLE_XZ: + case PATH_EXT_ANGLE_REL_XZ: + for (final Path p : tree.list()) + stat.addValue(p.getExtensionAngleXZ(PATH_EXT_ANGLE_REL_XZ.equals(m))); + break; + case PATH_EXT_ANGLE_ZY: + case PATH_EXT_ANGLE_REL_ZY: + for (final Path p : tree.list()) + stat.addValue(p.getExtensionAngleZY(PATH_EXT_ANGLE_REL_ZY.equals(m))); + break; + case PATH_FRACTAL_DIMENSION: + tree.list().forEach(p -> stat.addValue(p.getFractalDimension())); + break; + case PATH_FRAME: + for (final Path p : tree.list()) { + stat.addValue(p.getFrame()); + } + break; + case PATH_LENGTH: + for (final Path p : tree.list()) + stat.addValue(p.getLength()); + break; + case PATH_MEAN_RADIUS: + for (final Path p : tree.list()) { + stat.addValue(p.getMeanRadius()); + } + break; + case PATH_N_SPINES: + for (final Path p : tree.list()) { + stat.addValue(p.getSpineOrVaricosityCount()); + } + break; + case PATH_ORDER: + for (final Path p : tree.list()) { + stat.addValue(p.getOrder()); + } + break; + case PATH_SPINE_DENSITY: + for (final Path p : tree.list()) { + stat.addValue(p.getSpineOrVaricosityCount() / p.getLength()); + } + break; + case PATH_SURFACE_AREA: + for (final Path p : tree.list()) + stat.addValue(p.getApproximatedSurface()); + break; + case PATH_VOLUME: + for (final Path p : tree.list()) + stat.addValue(p.getApproximatedVolume()); + break; + case PRIMARY_EXTENSION_ANGLE_XY: + try { + getPrimaryBranches().forEach(b -> stat.addValue(b.getExtensionAngleXY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case PRIMARY_EXTENSION_ANGLE_XZ: + try { + getPrimaryBranches().forEach(b -> stat.addValue(b.getExtensionAngleXZ(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case PRIMARY_EXTENSION_ANGLE_ZY: + try { + getPrimaryBranches().forEach(b -> stat.addValue(b.getExtensionAngleZY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case PRIMARY_LENGTH: + for (final Path p : getPrimaryBranches()) + stat.addValue(p.getLength()); + break; + case REMOTE_BIF_ANGLES: + try { + for (final double angle : getRemoteBifAngles()) + stat.addValue(angle); + } catch (final IllegalArgumentException iae) { + SNTUtils.log("Error: " + iae.getMessage()); + stat.addValue(Double.NaN); + } + break; + case SHOLL_DECAY: + case SHOLL_KURTOSIS: + case SHOLL_MAX_FITTED: + case SHOLL_MAX_FITTED_RADIUS: + case SHOLL_MAX_VALUE: + case SHOLL_MEAN_VALUE: + case SHOLL_N_MAX: + case SHOLL_N_SECONDARY_MAX: + case SHOLL_POLY_FIT_DEGREE: + case SHOLL_RAMIFICATION_INDEX: + case SHOLL_SKEWNESS: + case SHOLL_SUM_VALUE: + stat.addValue(getShollMetric(m).doubleValue()); + break; + case STRAHLER_NUMBER: + stat.addValue(getStrahlerNumber()); + break; + case STRAHLER_RATIO: + stat.addValue(getStrahlerBifurcationRatio()); + break; + case SURFACE_AREA: + stat.addValue(tree.getApproximatedSurface()); + break; + case TERMINAL_EXTENSION_ANGLE_XY: + try { + getTerminalBranches().forEach(b -> stat.addValue(b.getExtensionAngleXY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case TERMINAL_EXTENSION_ANGLE_XZ: + try { + getTerminalBranches().forEach(b -> stat.addValue(b.getExtensionAngleXZ(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case TERMINAL_EXTENSION_ANGLE_ZY: + try { + getTerminalBranches().forEach(b -> stat.addValue(b.getExtensionAngleZY(false))); + } catch (final IllegalArgumentException ignored) { + stat.addValue(Double.NaN); + } + break; + case TERMINAL_LENGTH: + for (final Path p : getTerminalBranches()) + stat.addValue(p.getLength()); + break; + case VALUES: + for (final Path p : tree.list()) { + if (!p.hasNodeValues()) continue; + for (int i = 0; i < p.size(); i++) { + stat.addValue(p.getNodeValue(i)); + } + } + if (stat.getN() == 0) throw new IllegalArgumentException("Tree has no values assigned"); + break; + case VOLUME: + stat.addValue(tree.getApproximatedVolume()); + break; + case WIDTH: + stat.addValue(getWidth()); + break; + case X_COORDINATES: + for (final Path p : tree.list()) { + for (int i = 0; i < p.size(); i++) { + stat.addValue(p.getNode(i).x); + } + } + break; + case Y_COORDINATES: + for (final Path p : tree.list()) { + for (int i = 0; i < p.size(); i++) { + stat.addValue(p.getNode(i).y); + } + } + break; + case Z_COORDINATES: + for (final Path p : tree.list()) { + for (int i = 0; i < p.size(); i++) { + stat.addValue(p.getNode(i).z); + } + } + break; + default: + throw new IllegalArgumentException("Unrecognized parameter " + measurement); + } + } + + protected boolean lastDstatsCanBeRecycled(final String normMeasurement) { + return (lastDstats != null && tree.size() == lastDstats.size && normMeasurement.equals(lastDstats.measurement)); + } + + /** + * Generates detailed summaries in which measurements are grouped by SWC-type + * flags + * + * @see #summarize(String, boolean) + */ + @Override + public void run() { + if (tree.list() == null || tree.list().isEmpty()) { + cancel("No Paths to Measure"); + return; + } + if (statusService != null) statusService.showStatus("Measuring Paths..."); + summarize(true); + if (statusService != null) statusService.clearStatus(); + } + + protected Number getShollMetric(final String metric) { + final String fMetric = metric.substring(metric.indexOf("Sholl: ") + 6).trim(); + return getShollAnalyzer().getSingleValueMetrics().getOrDefault(fMetric, Double.NaN); + } + + /** + * Convenience method to initialize a {@link NodeStatistics} from all the nodes of the Tree being analyzed. + * @return the NodeStatistics instance + */ + public NodeStatistics getNodeStatistics() { + return getNodeStatistics("all"); + } + + /** + * Convenience method to initialize a {@link NodeStatistics} from a subset of the nodes of the Tree being analyzed. + * + * @param type the vertex type (e.g., "tips"/"end-points", "junctions"/"branch points", "all") + * @return the NodeStatistics instance + * @see DirectedWeightedGraph#getNodeStatistics(String) + */ + public NodeStatistics getNodeStatistics(final String type) { + return tree.getGraph().getNodeStatistics(type); + } + + /** + * Convenience method to obtain a single-value metric from {@link ConvexHullAnalyzer} init + * @param metric + * @return the convex hull metric + * @see #getConvexAnalyzer() + * @see ConvexHullAnalyzer#get(String) + */ + public double getConvexHullMetric(final String metric) { + final String fMetric = metric.substring(metric.indexOf("Convex Hull: ") + 13).trim(); + return getConvexAnalyzer().get(fMetric); + } + + /** + * Gets the {@link ConvexHullAnalyzer} instance associated with this TreeStatistics instance. + * + * @return the ConvexHullAnalyzer instance + */ + public ConvexHullAnalyzer getConvexAnalyzer() { + if (convexAnalyzer == null) { + convexAnalyzer = new ConvexHullAnalyzer(tree); + convexAnalyzer.setContext((getContext() == null) ? SNTUtils.getContext() : getContext()); + } + return convexAnalyzer; + } + + /** + * Retrieves the brain compartments (neuropil labels) associated with the Tree being measured + * innervated by the analyzed neuron. + * + * @return the set of brain compartments ({@link BrainAnnotation}s) + * @see AllenCompartment + */ + public Set getAnnotations() { + final HashSet set = new HashSet<>(); + for (final Path path : tree.list()) { + for (int i = 0; i < path.size(); i++) { + final BrainAnnotation annotation = path.getNodeAnnotation(i); + if (annotation != null) set.add(annotation); + } + } + return set; + } + + /** + * Retrieves the brain compartments (neuropil labels) associated with the Tree being measured + * innervated by the analyzed neuron. + * + * @param depth the max. ontological depth of the compartments to be retrieved + * @return the set of brain compartments ({@link BrainAnnotation}s) + * @see AllenCompartment + */ + public Set getAnnotations(final int depth) { + final Set filteredAnnotations = new HashSet<>(); + getAnnotations().forEach(annot -> { + final int annotDepth = annot.getOntologyDepth(); + if (annotDepth > depth) { + filteredAnnotations.add(annot.getAncestor(depth - annotDepth)); + } else { + filteredAnnotations.add(annot); + } + }); + return filteredAnnotations; + } + + /** + * Gets the cable length of primary branches. + * + * @return the length sum of all primary branches + * @see #getPrimaryBranches() + */ + public double getPrimaryLength() { + if (primaryBranches == null) getPrimaryBranches(); + return sumLength(primaryBranches); + } + + /** + * Gets the cable length of inner branches + * + * @return the length sum of all inner branches + * @see #getInnerBranches() + */ + public double getInnerLength() { + if (innerBranches == null) getInnerBranches(); + return sumLength(innerBranches); + } + + /** + * Gets the cable length of terminal branches + * + * @return the length sum of all terminal branches + * @see #getTerminalBranches() + */ + public double getTerminalLength() { + if (terminalBranches == null) getTerminalBranches(); + return sumLength(terminalBranches); + } + + /** + * Gets the highest {@link sc.fiji.snt.Path#getOrder() path order} of the analyzed tree + * + * @return the highest Path order, or -1 if Paths in the Tree have no defined + * order + * @see #getStrahlerNumber() + */ + public int getHighestPathOrder() { + int root = -1; + for (final Path p : tree.list()) { + final int order = p.getOrder(); + if (order > root) root = order; + } + return root; + } + + /** + * Checks whether this tree is topologically valid, i.e., contains only one root and no loops. + * + * @return true, if Tree is valid, false otherwise + */ + public boolean isValid() { + if (sAnalyzer == null) sAnalyzer = new StrahlerAnalyzer(tree); + try { + sAnalyzer.getGraph(); + return true; + } catch (final IllegalArgumentException ignored) { + return false; + } + } + + /** + * Gets the highest {@link StrahlerAnalyzer#getRootNumber() Strahler number} of the analyzed tree. + * + * @return the highest Strahler (root) number order + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public int getStrahlerNumber() throws IllegalArgumentException { + getStrahlerAnalyzer(); + return sAnalyzer.getRootNumber(); + } + + /** + * Gets the {@link StrahlerAnalyzer} instance associated with this TreeStatistics instance + * + * @return the StrahlerAnalyzer instance associated with this TreeStatistics instance + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public StrahlerAnalyzer getStrahlerAnalyzer() throws IllegalArgumentException { + if (sAnalyzer == null) sAnalyzer = new StrahlerAnalyzer(tree); + return sAnalyzer; + } + + /** + * Gets the {@link ShollAnalyzer} instance associated with this TreeStatistics instance. Note + * that changes to {@link ShollAnalyzer} must be performed before retrieving + * Sholl related metrics, i.e., before calling {@link #measure(Collection, boolean)}, etc. + * + * @return the ShollAnalyzer instance associated with this TreeStatistics instance + */ + public ShollAnalyzer getShollAnalyzer() { + if (shllAnalyzer == null) shllAnalyzer = new ShollAnalyzer(tree, this); + return shllAnalyzer; + } + + /** + * Gets the average {@link StrahlerAnalyzer#getAvgBifurcationRatio() Strahler bifurcation ratio} of analyzed tree. + * + * @return the average bifurcation ratio + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public double getStrahlerBifurcationRatio() throws IllegalArgumentException { + getStrahlerAnalyzer(); + return sAnalyzer.getAvgBifurcationRatio(); + } + + /** + * Gets the number of branches in the analyzed tree. + * + * @return the number of branches + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public int getNBranches() throws IllegalArgumentException { + return getBranches().size(); + } + + /** + * Gets the number of nodes in the analyzed tree. + * + * @return the number of nodes + */ + public long getNNodes() { + return tree.getNodesCount(); + } + + /** + * Gets all the branches in the analyzed tree. A branch is defined as the Path composed of all the nodes between + * two branching points or between one branching point and a termination point. + * + * @return the list of branches as Path objects. + * @throws IllegalArgumentException if tree contains multiple roots or loops + * @see StrahlerAnalyzer#getBranches() + */ + public List getBranches() throws IllegalArgumentException { + getStrahlerAnalyzer(); + return sAnalyzer.getBranches().values().stream().flatMap(List::stream).collect(Collectors.toList()); + } + + /** + * Gets average {@link Path#getContraction() contraction} for all the branches of the analyzed tree. + * + * @return the average branch contraction + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public double getAvgContraction() throws IllegalArgumentException { + double contraction = 0; + final List branches = getBranches(); + for (final Path p : branches) { + final double pContraction = p.getContraction(); + if (!Double.isNaN(pContraction)) contraction += pContraction; + } + return contraction / branches.size(); + } + + /** + * Gets the average no. of nodes (fragmentation) for all the branches of the analyzed tree. + * + * @return the average no. of branch nodes (average branch fragmentation) + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public double getAvgFragmentation() { + double fragmentation = 0; + final List branches = getBranches(); + for (final Path p : branches) { + fragmentation += p.size(); + } + return fragmentation / branches.size(); + } + + /** + * Gets average length for all the branches of the analyzed tree. + * + * @return the average branch length + * @throws IllegalArgumentException if tree contains multiple roots or loops + */ + public double getAvgBranchLength() throws IllegalArgumentException { + final List branches = getBranches(); + return sumLength(getBranches()) / branches.size(); + } + + /** + * Gets the angle between each bifurcation point and its children in the simplified graph, which comprise either + * branch points or terminal nodes. Note that branch points with more than 2 children are ignored. + * + * @return the list of remote bifurcation angles + * @throws IllegalArgumentException if the tree contains multiple roots or loops + */ + public List getRemoteBifAngles() throws IllegalArgumentException { + final DirectedWeightedGraph sGraph = tree.getGraph(true); + final List branchPoints = sGraph.getBPs(); + final List angles = new ArrayList<>(); + for (final SWCPoint bp : branchPoints) { + final List children = Graphs.successorListOf(sGraph, bp); + // Only consider bifurcations + if (children.size() > 2) { + continue; + } + final SWCPoint c0 = children.get(0); + final SWCPoint c1 = children.get(1); + // Get vector for each parent-child link + final double[] v0 = new double[]{c0.getX() - bp.getX(), c0.getY() - bp.getY(), c0.getZ() - bp.getZ()}; + final double[] v1 = new double[]{c1.getX() - bp.getX(), c1.getY() - bp.getY(), c1.getZ() - bp.getZ()}; + // Dot product + double dot = 0.0; + for (int i = 0; i < v0.length; i++) { + dot += v0[i] * v1[i]; + } + final double cosineAngle = dot / (Math.sqrt(v0[0] * v0[0] + v0[1] * v0[1] + v0[2] * v0[2]) * Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2])); + final double angleRadians = Math.acos(cosineAngle); + final double angleDegrees = angleRadians * (180.0 / Math.PI); + angles.add(angleDegrees); + } + return angles; + } + + /** + * Gets the average remote bifurcation angle of the analyzed tree. Note that branch points with more than 2 + * children are ignored during the computation. + * + * @return the average remote bifurcation angle + * @throws IllegalArgumentException if the tree contains multiple roots or loops + */ + public double getAvgRemoteBifAngle() throws IllegalArgumentException { + final List angles = getRemoteBifAngles(); + double sumAngles = 0.0; + for (final double a : angles) { + sumAngles += a; + } + return sumAngles / angles.size(); + } + + /** + * Gets the partition asymmetry at each bifurcation point in the analyzed tree. + * Note that branch points with more than 2 children are ignored. + * + * @return a list containing the partition asymmetry at each bifurcation point + * @throws IllegalArgumentException if the tree contains multiple roots or loops + */ + public List getPartitionAsymmetry() throws IllegalArgumentException { + final DirectedWeightedGraph sGraph = tree.getGraph(true); + final List branchPoints = sGraph.getBPs(); + final List resultList = new ArrayList<>(); + for (final SWCPoint bp : branchPoints) { + final List children = Graphs.successorListOf(sGraph, bp); + // Only consider bifurcations + if (children.size() > 2) { + continue; + } + final List tipCounts = new ArrayList<>(); + for (final SWCPoint child : children) { + int count = 0; + final DepthFirstIterator dfi = sGraph.getDepthFirstIterator(child); + while (dfi.hasNext()) { + final SWCPoint node = dfi.next(); + if (Graphs.successorListOf(sGraph, node).isEmpty()) { + count++; + } + } + tipCounts.add(count); + } + double asymmetry; + // Make sure we avoid getting NaN + if (tipCounts.get(0).equals(tipCounts.get(1))) { + asymmetry = 0.0; + } else { + asymmetry = (double) Math.abs(tipCounts.get(0) - tipCounts.get(1)) / (tipCounts.get(0) + tipCounts.get(1) - 2); + } + resultList.add(asymmetry); + } + return resultList; + } + + /** + * Gets the average partition asymmetry of the analyzed tree. + * Note that branch points with more than 2 children are ignored during the computation. + * + * @return the average partition asymmetry + * @throws IllegalArgumentException if the tree contains multiple roots or loops + */ + public double getAvgPartitionAsymmetry() throws IllegalArgumentException { + final List asymmetries = getPartitionAsymmetry(); + double sumAsymmetries = 0.0; + for (final double a : asymmetries) { + sumAsymmetries += a; + } + return sumAsymmetries / asymmetries.size(); + } + + /** + * Gets the fractal dimension of each branch in the analyzed tree. + * Note that branches with less than 5 points are ignored. + * + * @return a list containing the fractal dimension of each branch + * @throws IllegalArgumentException if the tree contains multiple roots or loops + * @see StrahlerAnalyzer#getBranches() + * @see Path#getFractalDimension() + */ + public List getFractalDimension() throws IllegalArgumentException { + final List fractalDims = new ArrayList<>(); + for (final Path b : getBranches()) { + final double fDim = b.getFractalDimension(); + if (!Double.isNaN(fDim)) fractalDims.add(fDim); + } + return fractalDims; + } + + /** + * Gets the average fractal dimension of the analyzed tree. + * Note that branches with less than 5 points are ignored during the computation. + * + * @return the average fractal dimension + * @throws IllegalArgumentException if the tree contains multiple roots or loops + */ + public double getAvgFractalDimension() throws IllegalArgumentException { + final List fractalDims = getFractalDimension(); + double sumDims = 0.0; + for (final double fDim : fractalDims) { + sumDims += fDim; + } + return sumDims / fractalDims.size(); + + } + + /** + * Gets the number of spines/varicosities that have been (manually) assigned to + * tree being analyzed. + * + * @return the number of spines/varicosities + */ + public int getNoSpinesOrVaricosities() { + return tree.list().stream().mapToInt(Path::getSpineOrVaricosityCount).sum(); + } + + /** + * Gets the overall density of spines/varicosities associated with this tree + * + * @return the spine/varicosity density (same as + * {@code getNoSpinesOrVaricosities()/getCableLength()}) + */ + public double getSpineOrVaricosityDensity() { + return getNoSpinesOrVaricosities() / getCableLength(); + } + + /** + * Updates and displays the measurements table. If {@link org.scijava.Context} has not been set + * (running headless!?), table is printed to Console. + */ + public void updateAndDisplayTable() { + if (getContext() == null) { + System.out.println(SNTTable.toString(table, 0, table.getRowCount() - 1)); + return; + } + final String displayName = (tableTitle == null) ? "SNT Measurements" : tableTitle; + final Display display = displayService.getDisplay(displayName); + if (display != null) { + display.update(); + } else { + displayService.createDisplay(displayName, table); + } + } + + /** + * Returns the physical unit associated with the specified metric. + * E.g.: returns µm³ if metric is "volume" and coordinates of tree being measured are defined in µm. + * @param metric the metric to be queried (case-insensitive) + * @return physical unit + */ + public String getUnit(final String metric) { + final String m = metric.toLowerCase(); + if (TreeStatistics.WIDTH.equals(metric) || TreeStatistics.HEIGHT.equals(metric) || TreeStatistics.DEPTH.equals(metric) + || m.contains("length") || m.contains("radius") || m.contains("distance") || TreeStatistics.CONVEX_HULL_ELONGATION.equals(metric)) { + return getUnit(); + } else if (m.contains("volume")) { + return getUnit() + "³"; + } else if (m.contains("surface area")) { + return getUnit() + "²"; + } else if (TreeStatistics.CONVEX_HULL_SIZE.equals(metric)) { + return getUnit() + ((tree.is3D()) ? "³" : "²"); + } else if (TreeStatistics.CONVEX_HULL_BOUNDARY_SIZE.equals(metric)) { + return getUnit() + ((tree.is3D()) ? "²" : ""); + } + return ""; + } + + protected String getUnit() { + return (String) tree.getProperties().getOrDefault(TreeProperties.KEY_SPATIAL_UNIT, "? units"); + } + + protected int getCol(final String header) { + final String normHeader = AnalysisUtils.getMetricLabel(header, tree); + int idx = table.getColumnIndex(normHeader); + if (idx == -1) { + table.appendColumn(normHeader); + idx = table.getColumnCount() - 1; + } + return idx; + } + + /** + * Gets the no. of parsed paths. + * + * @return the number of paths + */ + public int getNPaths() { + return tree.list().size(); + } + + protected int getNFittedPaths() { + return fittedPathsCounter; + } + + /** + * Returns the width of the {@link sc.fiji.snt.util.BoundingBox} box of the tree being measured + * + * @return the bounding box width + */ + public double getWidth() { + return tree.getBoundingBox(true).width(); + } + + /** + * Returns the height of the {@link sc.fiji.snt.util.BoundingBox} box of the tree being measured + * + * @return the bounding box height + */ + public double getHeight() { + return tree.getBoundingBox(true).height(); + } + + /** + * Returns the depth of the {@link sc.fiji.snt.util.BoundingBox} box of the tree being measured + * + * @return the bounding box depth + */ + public double getDepth() { + return tree.getBoundingBox(true).depth(); + } + + /** + * Retrieves all the Paths in the analyzed Tree tagged as primary. + * + * @return the list of primary paths. + * @see #getPrimaryBranches() + */ + public List getPrimaryPaths() { + final List primaryPaths = new ArrayList<>(); + for (final Path p : tree.list()) { + if (p.isPrimary()) primaryPaths.add(p); + } + return primaryPaths; + } + + /** + * Retrieves the branches of highest + * {@link StrahlerAnalyzer#getHighestBranchOrder() Strahler order} in the Tree. + * This typically correspond to the most 'internal' branches of the Tree in + * direct sequence from the root. + * + * @return the list containing the "inner" branches. Note that these branches + * (Path segments) will not carry any connectivity information. + * @see #getPrimaryPaths() + * @see StrahlerAnalyzer#getBranches() + * @see StrahlerAnalyzer#getHighestBranchOrder() + */ + public List getInnerBranches() { + getStrahlerAnalyzer(); + innerBranches = sAnalyzer.getBranches(sAnalyzer.getHighestBranchOrder()); + return innerBranches; + } + + /** + * Retrieves the primary branches of the analyzed Tree. Primary branches (or + * root-associated) have origin in the Tree's root, extending to the closest + * branch/end-point. Note that a primary branch can also be terminal. + * + * @return the primary branches. Note that these branches (Path segments) will + * not carry any connectivity information. + * @see #getPrimaryPaths() + * @see StrahlerAnalyzer#getRootAssociatedBranches() + */ + public List getPrimaryBranches() { + getStrahlerAnalyzer(); + primaryBranches = sAnalyzer.getRootAssociatedBranches(); + return primaryBranches; + } + + /** + * Retrieves the terminal branches of the analyzed Tree. A terminal branch + * corresponds to the section of a terminal Path between its last branch-point + * and its terminal point (tip). A terminal branch can also be primary. + * + * @return the terminal branches. Note that as per + * {@link Path#getSection(int, int)}, these branches will not carry any + * connectivity information. + * @see #getPrimaryBranches + */ + public List getTerminalBranches() { + getStrahlerAnalyzer(); + terminalBranches = sAnalyzer.getBranches(1); + return terminalBranches; + } + + /** + * Gets the position of all the tips in the analyzed tree. + * + * @return the set of terminal points + */ + public Set getTips() { + + // retrieve all start/end points + tips = new HashSet<>(); + for (final Path p : tree.list()) { + tips.add(p.getNode(p.size() - 1)); + } + // now remove any joint-associated point + if (joints == null) getBranchPoints(); + tips.removeAll(joints); + return tips; + + } + + /** + * Gets the position of all the tips in the analyzed tree associated with the specified annotation. + * + * @param annot the BrainAnnotation to be queried (all of its children will be considered). Null not allowed. + * @return the branch points positions, or an empty set if no tips were retrieved. + */ + public Set getTips(final BrainAnnotation annot) { + return getTips(annot, true); + } + + /** + * Gets the position of all the tips in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. Null not allowed. + * @param includeChildren whether children of {@code annot} should be included. + * @return the branch points positions, or an empty set if no tips were retrieved. + */ + public Set getTips(final BrainAnnotation annot, final boolean includeChildren) { + if (tips == null) getTips(); + final HashSet fTips = new HashSet<>(); + for (final PointInImage tip : tips) { + final BrainAnnotation annotation = tip.getAnnotation(); + if (annotation != null && contains(annot, annotation, includeChildren)) fTips.add(tip); + } + return fTips; + } + + /** + * Gets the number of end points in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. All of its children will be considered + * @return the number of end points + */ + public int getNTips(final BrainAnnotation annot) { + return getNTips(annot, true); + } + + /** + * Gets the number of end points in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. + * @param includeChildren whether children of {@code annot} should be included. + * @return the number of end points + */ + public int getNTips(final BrainAnnotation annot, final boolean includeChildren) { + return getTips(annot, includeChildren).size(); + } + + /** + * Gets the number of end points in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. All of its children will be considered + * @return the number of end points + */ + public double getNTipsNorm(final BrainAnnotation annot) { + return getNTipsNorm(annot, true); + } + + /** + * Gets the percentage of end points in the analyzed tree associated with the + * specified annotation + * + * @param annot the BrainAnnotation to be queried. + * @param includeChildren whether children of {@code annot} should be included + * @return the ratio between the no. of branch points associated with + * {@code annot} and the total number of end points in the tree. + */ + public double getNTipsNorm(final BrainAnnotation annot, final boolean includeChildren) { + return (double) (getNTips(annot, includeChildren)) / (double) (tips.size()); + } + + /** + * Gets the position of all the branch points in the analyzed tree. + * + * @return the branch points positions + */ + public Set getBranchPoints() { + joints = new HashSet<>(); + for (final Path p : tree.list()) { + joints.addAll(p.getJunctionNodes()); + } + return joints; + } + + /** + * Gets the position of all the branch points in the analyzed tree associated + * with the specified annotation. + * + * @param annot the BrainAnnotation to be queried. All of its children are + * considered. + * @return the branch points positions, or an empty set if no branch points were retrieved + */ + public Set getBranchPoints(final BrainAnnotation annot) { + return getBranchPoints(annot, true); + } + + /** + * Gets the position of all the branch points in the analyzed tree associated + * with the specified annotation. + * + * @param annot the BrainAnnotation to be queried. + * @param includeChildren whether children of {@code annot} should be included + * @return the branch points positions, or an empty set if no branch points were retrieved + */ + public Set getBranchPoints(final BrainAnnotation annot, final boolean includeChildren) { + if (joints == null) getBranchPoints(); + final HashSet fJoints = new HashSet<>(); + for (final PointInImage joint : joints) { + final BrainAnnotation annotation = joint.getAnnotation(); + if (annotation != null && contains(annot, annotation, includeChildren)) fJoints.add(joint); + } + return fJoints; + } + + /** + * Gets the number of branch points in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. All of its children are + * considered. + * @return the number of branch points + */ + public int getNBranchPoints(final BrainAnnotation annot) { + return getNBranchPoints(annot, true); + } + + /** + * Gets the number of branch points in the analyzed tree associated with the + * specified annotation. + * + * @param annot the BrainAnnotation to be queried. + * @param includeChildren whether children of {@code annot} should be included + * @return the number of branch points + */ + public int getNBranchPoints(final BrainAnnotation annot, final boolean includeChildren) { + return getBranchPoints(annot, includeChildren).size(); + } + + /** + * Gets the percentage of branch points in the analyzed tree associated with the + * specified annotation + * + * @param annot the BrainAnnotation to be queried. All of its children are + * considered. + * @return the ratio between the no. of branch points associated with + * {@code annot} and the total number of branch points in the tree. + */ + public double getNBranchPointsNorm(final BrainAnnotation annot) { + return getNBranchPointsNorm(annot, true); + } + + /** + * Gets the percentage of branch points in the analyzed tree associated with the + * specified annotation + * + * @param annot the BrainAnnotation to be queried. + * @param includeChildren whether children of {@code annot} should be included + * @return the ratio between the no. of branch points associated with + * {@code annot} and the total number of branch points in the tree. + */ + public double getNBranchPointsNorm(final BrainAnnotation annot, final boolean includeChildren) { + return (double) (getNBranchPointsNorm(annot, includeChildren)) / (double) (joints.size()); + } + + /** + * Gets the cable length. + * + * @return the cable length of the tree + */ + public double getCableLength() { + return sumLength(tree.list()); + } + + /** + * Gets the cable length associated with the specified compartment (neuropil + * label). + * + * @param compartment the query compartment (null not allowed). All of its + * children will be considered + * @return the filtered cable length + */ + public double getCableLength(final BrainAnnotation compartment) { + return getCableLength(compartment, true); + } + + /** + * Gets the cable length associated with the specified compartment (neuropil + * label) as a ratio of total length. + * + * @param compartment the query compartment (null not allowed). All of its + * children will be considered + * @return the filtered cable length normalized to total cable length + */ + public double getCableLengthNorm(final BrainAnnotation compartment) { + return getCableLength(compartment, true) / getCableLength(); + } + + /** + * Gets the cable length associated with the specified compartment (neuropil + * label) as a ratio of total length. + * + * @param compartment the query compartment (null not allowed) + * @param includeChildren whether children of {@code compartment} should be + * included + * @return the filtered cable length normalized to total cable length + */ + public double getCableLengthNorm(final BrainAnnotation compartment, final boolean includeChildren) { + return getCableLength(compartment, includeChildren) / getCableLength(); + } + + /** + * Gets the cable length associated with the specified compartment (neuropil + * label). + * + * @param compartment the query compartment (null not allowed) + * @param includeChildren whether children of {@code compartment} should be included + * @return the filtered cable length + */ + public double getCableLength(final BrainAnnotation compartment, final boolean includeChildren) { + final DirectedWeightedGraph graph = tree.getGraph(); + final NodeStatistics nodeStats = new NodeStatistics<>(graph.vertexSet()); + final DirectedWeightedSubgraph subgraph = graph.getSubgraph(new HashSet<>(nodeStats.get(compartment, includeChildren))); + return subgraph.sumEdgeWeights(true); + } + + protected boolean contains(final BrainAnnotation annot, final BrainAnnotation annotToBeTested, final boolean includeChildren) { + if (includeChildren) return annot.equals(annotToBeTested) || annot.isParentOf(annotToBeTested); + return annot.equals(annotToBeTested); + } + + private double sumLength(final Collection paths) { + double totalLength = 0d; + for (final Path p : paths) { + if (p.getStartJoins() != null) { + totalLength += p.getStartJoinsPoint().distanceTo(p.getNode(0)); + } + totalLength += p.getLength(); + } + return totalLength; + } + + static class StatisticsInstance { + + private SummaryStatistics sStatistics; + private DescriptiveStatistics dStatistics; + + StatisticsInstance(final SummaryStatistics sStatistics) { + this.sStatistics = sStatistics; + } + + StatisticsInstance(final DescriptiveStatistics dStatistics) { + this.dStatistics = dStatistics; + } + + void addValue(final double value) { + if (sStatistics != null) sStatistics.addValue(value); + else dStatistics.addValue(value); + } + + long getN() { + return (sStatistics != null) ? sStatistics.getN() : dStatistics.getN(); + } + + } + + class LastDstats { + + final DescriptiveStatistics dStats; + private final String measurement; + private final int size; + + LastDstats(final String measurement, final DescriptiveStatistics dStats) { + this.measurement = measurement; + this.dStats = dStats; + size = tree.size(); + } + } + + class HDPlus extends HistogramDatasetPlus { + final String measurement; + + HDPlus(final String measurement) { + this(measurement, true); + } + + HDPlus(final String measurement, final boolean retrieveValues) { + super(); + this.measurement = measurement; + if (retrieveValues) { + getDescriptiveStats(measurement); + for (final double v : lastDstats.dStats.getValues()) { + values.add(v); + } + } + } + } + + /* IDE debug method */ + public static void main(final String[] args) { + final MouseLightLoader loader = new MouseLightLoader("AA0015"); + final Tree axon = loader.getTree("axon"); + final TreeStatistics tStats = new TreeStatistics(axon); + final int depth = 6;// Integer.MAX_VALUE; + + // retrieve some metrics: + tStats.getHistogram("fractal dimension").show(); + NodeStatistics nStats = new NodeStatistics<>(tStats.getTips()); + SNTChart hist = nStats.getAnnotatedHistogram(depth); + hist.annotate("No. of tips: " + tStats.getTips().size()); + hist.show(); + + // retrieve annotated lengths + // AllenUtils.assignHemisphereTags(axon.getGraph()); + hist = tStats.getAnnotatedLengthHistogram(depth); + AllenCompartment somaCompartment = loader.getSomaCompartment(); + if (somaCompartment.getOntologyDepth() > depth) + somaCompartment = somaCompartment.getAncestor(depth - somaCompartment.getOntologyDepth()); + hist.annotateCategory(somaCompartment.acronym(), "soma"); + hist.show(); + hist = tStats.getAnnotatedLengthHistogram(depth, "left"); + hist.annotateCategory(somaCompartment.acronym(), "soma"); + hist.show(); + hist = tStats.getAnnotatedLengthHistogram(depth, "right"); + hist.annotateCategory(somaCompartment.acronym(), "soma"); + hist.show(); + hist = tStats.getAnnotatedLengthHistogram(depth, "ratio"); + hist.annotateCategory(somaCompartment.acronym(), "soma"); + hist.setFontSize(25); + hist.show(); + hist = tStats.getFlowPlot("Cable length", tStats.getAnnotatedLength(depth).keySet()); + hist.show(); + } } diff --git a/src/main/java/sc/fiji/snt/analysis/graph/AnnotationGraph.java b/src/main/java/sc/fiji/snt/analysis/graph/AnnotationGraph.java index e5763617..975e30a4 100644 --- a/src/main/java/sc/fiji/snt/analysis/graph/AnnotationGraph.java +++ b/src/main/java/sc/fiji/snt/analysis/graph/AnnotationGraph.java @@ -26,7 +26,6 @@ import org.jgrapht.util.SupplierUtil; import sc.fiji.snt.Tree; import sc.fiji.snt.analysis.MultiTreeStatistics; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.annotation.AllenUtils; import sc.fiji.snt.annotation.BrainAnnotation; @@ -160,7 +159,7 @@ private void initTips(final Collection trees, int minTipCount, int maxOnto if (!containsVertex(rootAnnotation)) { addVertex(rootAnnotation); } - final Set tips = new TreeAnalyzer(tree).getTips(); + final Set tips = new TreeStatistics(tree).getTips(); Map countMap = new HashMap<>(); for (final PointInImage tip : tips) { BrainAnnotation tipAnnotation = tip.getAnnotation(); @@ -213,7 +212,7 @@ private void initBranchPoints(final Collection trees, int minBranchCount, if (!containsVertex(rootAnnotation)) { addVertex(rootAnnotation); } - final Set branches = new TreeAnalyzer(tree).getBranchPoints(); + final Set branches = new TreeStatistics(tree).getBranchPoints(); Map countMap = new HashMap<>(); for (final PointInImage branch : branches) { BrainAnnotation branchAnnotation = branch.getAnnotation(); @@ -347,13 +346,13 @@ private BrainAnnotation getMatchedAnnotation(final BrainAnnotation query, final return query; } - private double getNodeCount(final TreeAnalyzer ta, BrainAnnotation annot, final String nodeType) { + private double getNodeCount(final TreeStatistics ts, BrainAnnotation annot, final String nodeType) { if (TIPS.equals(nodeType)) { - return ta.getTips(annot).size(); + return ts.getTips(annot).size(); } else if (BRANCH_POINTS.equals(nodeType)) { - return ta.getBranchPoints(annot).size(); + return ts.getBranchPoints(annot).size(); } else if (LENGTH.equals(nodeType)) { - return ta.getCableLength(annot); + return ts.getCableLength(annot); } throw new IllegalArgumentException("Unknown nodeType: " + nodeType); } @@ -372,7 +371,7 @@ private void initNodes(final Collection trees, final Collection countMap = new HashMap<>(); for (final BrainAnnotation annot : annotations) { if (annot != null) diff --git a/src/main/java/sc/fiji/snt/gui/PathManagerUISearchableBar.java b/src/main/java/sc/fiji/snt/gui/PathManagerUISearchableBar.java index cdcdf9fc..38ec999b 100644 --- a/src/main/java/sc/fiji/snt/gui/PathManagerUISearchableBar.java +++ b/src/main/java/sc/fiji/snt/gui/PathManagerUISearchableBar.java @@ -38,7 +38,7 @@ import sc.fiji.snt.Path; import sc.fiji.snt.PathManagerUI; -import sc.fiji.snt.analysis.PathAnalyzer; +import sc.fiji.snt.analysis.PathStatistics; import sc.fiji.snt.gui.cmds.FilterOrTagPathsByAngleCmd; import sc.fiji.snt.gui.cmds.SWCTypeFilterCmd; import sc.fiji.snt.util.SNTColor; @@ -200,14 +200,14 @@ private JMenu getMorphoFilterMenu() { }); morphoFilteringMenu.add(mi1); morphoFilteringMenu.add(angleFilterMenuItem()); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.N_CHILDREN, "")); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.N_NODES, "")); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.N_SPINES, "")); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.PATH_CONTRACTION, "")); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.PATH_LENGTH, + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.N_CHILDREN, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.N_NODES, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.N_SPINES, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.PATH_CONTRACTION, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.PATH_LENGTH, pmui.getPathAndFillManager().getBoundingBox(false).getUnit())); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.PATH_MEAN_RADIUS, "")); - morphoFilteringMenu.add(morphoFilterMenuItem(PathAnalyzer.PATH_ORDER, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.PATH_MEAN_RADIUS, "")); + morphoFilteringMenu.add(morphoFilterMenuItem(PathStatistics.PATH_ORDER, "")); return morphoFilteringMenu; } @@ -232,25 +232,25 @@ private JMenuItem morphoFilterMenuItem(final String pathAnalyzerMetric, final St final JMenuItem mi = new JMenuItem(pathAnalyzerMetric + "..."); mi.addActionListener(e -> doMorphoFiltering(pathAnalyzerMetric, unit)); switch (pathAnalyzerMetric) { - case PathAnalyzer.N_CHILDREN: + case PathStatistics.N_CHILDREN: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.CHILDREN)); break; - case PathAnalyzer.N_NODES: + case PathStatistics.N_NODES: mi.setIcon(IconFactory.getMenuIcon('#', true)); break; - case PathAnalyzer.N_SPINES: + case PathStatistics.N_SPINES: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.MAP_PIN)); break; - case PathAnalyzer.PATH_CONTRACTION: + case PathStatistics.PATH_CONTRACTION: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.STAIRS)); break; - case PathAnalyzer.PATH_LENGTH: + case PathStatistics.PATH_LENGTH: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.RULER_VERTICAL)); break; - case PathAnalyzer.PATH_MEAN_RADIUS: + case PathStatistics.PATH_MEAN_RADIUS: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.CIRCLE)); break; - case PathAnalyzer.PATH_ORDER: + case PathStatistics.PATH_ORDER: mi.setIcon(IconFactory.getMenuIcon(IconFactory.GLYPH.BRANCH_CODE)); break; default: @@ -371,7 +371,7 @@ private void doMorphoFiltering(final String property, final String unit) { double min = Double.MIN_VALUE; double max = Double.MAX_VALUE; if (s.contains("min") || s.contains("max")) { - final PathAnalyzer stats = new PathAnalyzer(filteredPaths, null); + final PathStatistics stats = new PathStatistics(filteredPaths, null); final SummaryStatistics summary = stats.getSummaryStats(property); min = summary.getMin(); max = summary.getMax(); @@ -409,38 +409,38 @@ public void doMorphoFiltering(final Collection paths, final String proper final Path p = iterator.next(); double value; switch (property) { - case PathAnalyzer.PATH_EXT_ANGLE_XY: - case PathAnalyzer.PATH_EXT_ANGLE_REL_XY: - value = p.getExtensionAngleXY(PathAnalyzer.PATH_EXT_ANGLE_REL_XY.equals(property)); + case PathStatistics.PATH_EXT_ANGLE_XY: + case PathStatistics.PATH_EXT_ANGLE_REL_XY: + value = p.getExtensionAngleXY(PathStatistics.PATH_EXT_ANGLE_REL_XY.equals(property)); break; - case PathAnalyzer.PATH_EXT_ANGLE_XZ: - case PathAnalyzer.PATH_EXT_ANGLE_REL_XZ: - value = p.getExtensionAngleXZ(PathAnalyzer.PATH_EXT_ANGLE_REL_XZ.equals(property)); + case PathStatistics.PATH_EXT_ANGLE_XZ: + case PathStatistics.PATH_EXT_ANGLE_REL_XZ: + value = p.getExtensionAngleXZ(PathStatistics.PATH_EXT_ANGLE_REL_XZ.equals(property)); break; - case PathAnalyzer.PATH_EXT_ANGLE_ZY: - case PathAnalyzer.PATH_EXT_ANGLE_REL_ZY: - value = p.getExtensionAngleZY(PathAnalyzer.PATH_EXT_ANGLE_REL_ZY.equals(property)); + case PathStatistics.PATH_EXT_ANGLE_ZY: + case PathStatistics.PATH_EXT_ANGLE_REL_ZY: + value = p.getExtensionAngleZY(PathStatistics.PATH_EXT_ANGLE_REL_ZY.equals(property)); break; - case PathAnalyzer.PATH_LENGTH: + case PathStatistics.PATH_LENGTH: case "Length": value = p.getLength(); break; - case PathAnalyzer.N_NODES: + case PathStatistics.N_NODES: value = p.size(); break; - case PathAnalyzer.PATH_MEAN_RADIUS: + case PathStatistics.PATH_MEAN_RADIUS: value = p.getMeanRadius(); break; - case PathAnalyzer.PATH_ORDER: + case PathStatistics.PATH_ORDER: value = p.getOrder(); break; - case PathAnalyzer.N_SPINES: + case PathStatistics.N_SPINES: value = p.getSpineOrVaricosityCount(); break; - case PathAnalyzer.PATH_CONTRACTION: + case PathStatistics.PATH_CONTRACTION: value = p.getContraction(); break; - case PathAnalyzer.N_CHILDREN: + case PathStatistics.N_CHILDREN: value = p.getChildren().size(); break; default: diff --git a/src/main/java/sc/fiji/snt/gui/cmds/FilterOrTagPathsByAngleCmd.java b/src/main/java/sc/fiji/snt/gui/cmds/FilterOrTagPathsByAngleCmd.java index dd5015aa..b8bb9966 100644 --- a/src/main/java/sc/fiji/snt/gui/cmds/FilterOrTagPathsByAngleCmd.java +++ b/src/main/java/sc/fiji/snt/gui/cmds/FilterOrTagPathsByAngleCmd.java @@ -30,7 +30,7 @@ import org.scijava.widget.NumberWidget; import sc.fiji.snt.Path; import sc.fiji.snt.PathManagerUI; -import sc.fiji.snt.analysis.PathAnalyzer; +import sc.fiji.snt.analysis.PathStatistics; import sc.fiji.snt.gui.GuiUtils; import java.util.*; @@ -116,11 +116,11 @@ public void run() { private String getMetric(final boolean relative) { switch (planeChoice) { case "XZ": - return (relative) ? PathAnalyzer.PATH_EXT_ANGLE_REL_XZ : PathAnalyzer.PATH_EXT_ANGLE_XZ; + return (relative) ? PathStatistics.PATH_EXT_ANGLE_REL_XZ : PathStatistics.PATH_EXT_ANGLE_XZ; case "ZY": - return (relative) ? PathAnalyzer.PATH_EXT_ANGLE_REL_ZY : PathAnalyzer.PATH_EXT_ANGLE_ZY; + return (relative) ? PathStatistics.PATH_EXT_ANGLE_REL_ZY : PathStatistics.PATH_EXT_ANGLE_ZY; default: - return (relative) ? PathAnalyzer.PATH_EXT_ANGLE_REL_XY : PathAnalyzer.PATH_EXT_ANGLE_XY; + return (relative) ? PathStatistics.PATH_EXT_ANGLE_REL_XY : PathStatistics.PATH_EXT_ANGLE_XY; } } diff --git a/src/main/java/sc/fiji/snt/gui/cmds/PersistenceAnalyzerCmd.java b/src/main/java/sc/fiji/snt/gui/cmds/PersistenceAnalyzerCmd.java index f86af391..c5a3f6bd 100644 --- a/src/main/java/sc/fiji/snt/gui/cmds/PersistenceAnalyzerCmd.java +++ b/src/main/java/sc/fiji/snt/gui/cmds/PersistenceAnalyzerCmd.java @@ -37,7 +37,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** - * + * GUI command persistence homology analyses. * @author Tiago Ferreira */ @Plugin(type = Command.class, visible = false, label = "Persistence Homology") diff --git a/src/main/java/sc/fiji/snt/plugin/AnalyzerCmd.java b/src/main/java/sc/fiji/snt/plugin/AnalyzerCmd.java index 7a01b0c0..27f66e83 100644 --- a/src/main/java/sc/fiji/snt/plugin/AnalyzerCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/AnalyzerCmd.java @@ -42,10 +42,7 @@ import net.imagej.ImageJ; import sc.fiji.snt.SNTService; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.MultiTreeStatistics; -import sc.fiji.snt.analysis.SNTTable; -import sc.fiji.snt.analysis.ShollAnalyzer; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.*; import sc.fiji.snt.gui.GuiUtils; import sc.fiji.snt.gui.cmds.CommonDynamicCmd; @@ -424,7 +421,7 @@ private void measure(final Collection trees, final List metrics) { while (it.hasNext()) { final Tree tree = it.next(); statusService.showStatus(index++, n, tree.getLabel()); - final TreeAnalyzer analyzer = new TreeAnalyzer(tree); + final TreeStatistics analyzer = new TreeStatistics(tree); analyzer.setContext(getContext()); analyzer.setTable(table, TABLE_TITLE); analyzer.measure(metrics, splitByType); // will display table diff --git a/src/main/java/sc/fiji/snt/plugin/PathAnalyzerCmd.java b/src/main/java/sc/fiji/snt/plugin/PathAnalyzerCmd.java index 938daabc..b65dcacf 100644 --- a/src/main/java/sc/fiji/snt/plugin/PathAnalyzerCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/PathAnalyzerCmd.java @@ -34,7 +34,7 @@ import sc.fiji.snt.Path; import sc.fiji.snt.SNTService; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.PathAnalyzer; +import sc.fiji.snt.analysis.PathStatistics; import sc.fiji.snt.analysis.SNTTable; import sc.fiji.snt.gui.GuiUtils; import sc.fiji.snt.gui.cmds.CommonDynamicCmd; @@ -179,44 +179,44 @@ public void run() { final List metrics = new ArrayList<>(); if (ct) { - metrics.add(PathAnalyzer.PATH_CHANNEL); - metrics.add(PathAnalyzer.PATH_FRAME); + metrics.add(PathStatistics.PATH_CHANNEL); + metrics.add(PathStatistics.PATH_FRAME); } if (convexHull) { - metrics.add(PathAnalyzer.CONVEX_HULL_SIZE); - metrics.add(PathAnalyzer.CONVEX_HULL_ELONGATION); - metrics.add(PathAnalyzer.CONVEX_HULL_ROUNDNESS); + metrics.add(PathStatistics.CONVEX_HULL_SIZE); + metrics.add(PathStatistics.CONVEX_HULL_ELONGATION); + metrics.add(PathStatistics.CONVEX_HULL_ROUNDNESS); } - if (fractalDimension) metrics.add(PathAnalyzer.PATH_FRACTAL_DIMENSION); - if (nBranchPoints) metrics.add(PathAnalyzer.N_BRANCH_POINTS); - if (nChildren) metrics.add(PathAnalyzer.N_CHILDREN); - if (pathContraction) metrics.add(PathAnalyzer.PATH_CONTRACTION); - if (pathFragmentation) metrics.add(PathAnalyzer.N_PATH_NODES); + if (fractalDimension) metrics.add(PathStatistics.PATH_FRACTAL_DIMENSION); + if (nBranchPoints) metrics.add(PathStatistics.N_BRANCH_POINTS); + if (nChildren) metrics.add(PathStatistics.N_CHILDREN); + if (pathContraction) metrics.add(PathStatistics.PATH_CONTRACTION); + if (pathFragmentation) metrics.add(PathStatistics.N_PATH_NODES); if (extensionAngles) { - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_XY); - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_XZ); - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_ZY); - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_REL_XY); - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_REL_XZ); - metrics.add(PathAnalyzer.PATH_EXT_ANGLE_REL_ZY); + metrics.add(PathStatistics.PATH_EXT_ANGLE_XY); + metrics.add(PathStatistics.PATH_EXT_ANGLE_XZ); + metrics.add(PathStatistics.PATH_EXT_ANGLE_ZY); + metrics.add(PathStatistics.PATH_EXT_ANGLE_REL_XY); + metrics.add(PathStatistics.PATH_EXT_ANGLE_REL_XZ); + metrics.add(PathStatistics.PATH_EXT_ANGLE_REL_ZY); } - if (pathLength) metrics.add(PathAnalyzer.PATH_LENGTH); - if (pathMeanRadius) metrics.add(PathAnalyzer.PATH_MEAN_RADIUS); - if (pathOrder) metrics.add(PathAnalyzer.PATH_ORDER); - if (pathNSpines) metrics.add(PathAnalyzer.PATH_N_SPINES); - if (pathSpineDensity) metrics.add(PathAnalyzer.PATH_SPINE_DENSITY); - if (pathSurfaceArea) metrics.add(PathAnalyzer.SURFACE_AREA); - if (pathVolume) metrics.add(PathAnalyzer.VOLUME); + if (pathLength) metrics.add(PathStatistics.PATH_LENGTH); + if (pathMeanRadius) metrics.add(PathStatistics.PATH_MEAN_RADIUS); + if (pathOrder) metrics.add(PathStatistics.PATH_ORDER); + if (pathNSpines) metrics.add(PathStatistics.PATH_N_SPINES); + if (pathSpineDensity) metrics.add(PathStatistics.PATH_SPINE_DENSITY); + if (pathSurfaceArea) metrics.add(PathStatistics.SURFACE_AREA); + if (pathVolume) metrics.add(PathStatistics.VOLUME); if (widthHeightDepth) { - metrics.add(PathAnalyzer.WIDTH); - metrics.add(PathAnalyzer.DEPTH); - metrics.add(PathAnalyzer.HEIGHT); + metrics.add(PathStatistics.WIDTH); + metrics.add(PathStatistics.DEPTH); + metrics.add(PathStatistics.HEIGHT); } if (metrics.isEmpty()) { error("No metrics chosen."); return; } - final PathAnalyzer analyzer = new PathAnalyzer(paths, ""); + final PathStatistics analyzer = new PathStatistics(paths, ""); analyzer.setContext(getContext()); analyzer.setTable(table, TABLE_TITLE); Collections.sort(metrics); diff --git a/src/main/java/sc/fiji/snt/plugin/PathOrderAnalysisCmd.java b/src/main/java/sc/fiji/snt/plugin/PathOrderAnalysisCmd.java index 45777086..1c746251 100644 --- a/src/main/java/sc/fiji/snt/plugin/PathOrderAnalysisCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/PathOrderAnalysisCmd.java @@ -44,9 +44,9 @@ import sc.fiji.snt.analysis.SNTChart; import sc.fiji.snt.analysis.SNTTable; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.Path; import sc.fiji.snt.Tree; +import sc.fiji.snt.analysis.TreeStatistics; /** * Command to perform {@link Path#getOrder() Path Ordering} analysis on a @@ -58,7 +58,7 @@ * * @author Tiago Ferreira */ -public class PathOrderAnalysisCmd extends TreeAnalyzer { +public class PathOrderAnalysisCmd extends TreeStatistics { @Parameter private PlotService plotService; @@ -106,7 +106,7 @@ public void compute() { // in a new list // now measure the group - final TreeAnalyzer analyzer = new TreeAnalyzer(new Tree(groupedPaths)); + final TreeStatistics analyzer = new TreeStatistics(new Tree(groupedPaths)); if (!analyzer.getParsedTree().isEmpty()) { tLengthMap.put(order, analyzer.getCableLength()); final int nPaths = analyzer.getNPaths(); diff --git a/src/main/java/sc/fiji/snt/plugin/PathSpineAnalysisCmd.java b/src/main/java/sc/fiji/snt/plugin/PathSpineAnalysisCmd.java index b8e3c3cf..24e50eb4 100644 --- a/src/main/java/sc/fiji/snt/plugin/PathSpineAnalysisCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/PathSpineAnalysisCmd.java @@ -45,7 +45,7 @@ import sc.fiji.snt.Path; import sc.fiji.snt.SNTService; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.PathAnalyzer; +import sc.fiji.snt.analysis.PathStatistics; import sc.fiji.snt.analysis.SNTChart; import sc.fiji.snt.analysis.SNTTable; import sc.fiji.snt.gui.cmds.CommonDynamicCmd; @@ -64,22 +64,22 @@ public class PathSpineAnalysisCmd extends CommonDynamicCmd { private PlotService plotService; @Parameter(label = "X-Axis Metric", choices = { "Path ID", // - PathAnalyzer.PATH_CHANNEL, PathAnalyzer.PATH_FRAME, // - PathAnalyzer.N_BRANCH_POINTS, PathAnalyzer.PATH_CONTRACTION, // - PathAnalyzer.PATH_EXT_ANGLE_XY, PathAnalyzer.PATH_EXT_ANGLE_XZ, PathAnalyzer.PATH_EXT_ANGLE_ZY,// - PathAnalyzer.PATH_EXT_ANGLE_REL_XY, PathAnalyzer.PATH_EXT_ANGLE_REL_XZ, PathAnalyzer.PATH_EXT_ANGLE_REL_ZY,// - PathAnalyzer.PATH_LENGTH, PathAnalyzer.PATH_MEAN_RADIUS, // - PathAnalyzer.PATH_ORDER, PathAnalyzer.PATH_SURFACE_AREA, // - PathAnalyzer.N_PATH_NODES, PathAnalyzer.PATH_VOLUME }) + PathStatistics.PATH_CHANNEL, PathStatistics.PATH_FRAME, // + PathStatistics.N_BRANCH_POINTS, PathStatistics.PATH_CONTRACTION, // + PathStatistics.PATH_EXT_ANGLE_XY, PathStatistics.PATH_EXT_ANGLE_XZ, PathStatistics.PATH_EXT_ANGLE_ZY,// + PathStatistics.PATH_EXT_ANGLE_REL_XY, PathStatistics.PATH_EXT_ANGLE_REL_XZ, PathStatistics.PATH_EXT_ANGLE_REL_ZY,// + PathStatistics.PATH_LENGTH, PathStatistics.PATH_MEAN_RADIUS, // + PathStatistics.PATH_ORDER, PathStatistics.PATH_SURFACE_AREA, // + PathStatistics.N_PATH_NODES, PathStatistics.PATH_VOLUME }) private String xAxisMetric; @Parameter(label = "Y-Axis Metric 1", choices = { // - PathAnalyzer.PATH_SPINE_DENSITY, PathAnalyzer.N_SPINES // + PathStatistics.PATH_SPINE_DENSITY, PathStatistics.N_SPINES // }) private String yAxisMetric1; @Parameter(label = "Y-axis Metric 2", required = false, choices = { NONE_OPTION, // - PathAnalyzer.PATH_SPINE_DENSITY, PathAnalyzer.N_SPINES // + PathStatistics.PATH_SPINE_DENSITY, PathStatistics.N_SPINES // }) private String yAxisMetric2; @@ -104,13 +104,13 @@ private void init() { getInfo().setLabel("Multimetric Plot..."); if (anyMetric) { final List metrics = Arrays.asList(NONE_OPTION, "Path ID", // - PathAnalyzer.PATH_CHANNEL, PathAnalyzer.PATH_FRAME, // - PathAnalyzer.PATH_CONTRACTION, PathAnalyzer.PATH_LENGTH, - PathAnalyzer.PATH_EXT_ANGLE_XY, PathAnalyzer.PATH_EXT_ANGLE_XZ, PathAnalyzer.PATH_EXT_ANGLE_ZY,// - PathAnalyzer.PATH_EXT_ANGLE_REL_XY, PathAnalyzer.PATH_EXT_ANGLE_REL_XZ, PathAnalyzer.PATH_EXT_ANGLE_REL_ZY,// - PathAnalyzer.PATH_ORDER, PathAnalyzer.PATH_N_SPINES, PathAnalyzer.PATH_SPINE_DENSITY, // - PathAnalyzer.N_BRANCH_POINTS, PathAnalyzer.PATH_MEAN_RADIUS, // - PathAnalyzer.PATH_SURFACE_AREA, PathAnalyzer.N_PATH_NODES, PathAnalyzer.PATH_VOLUME); + PathStatistics.PATH_CHANNEL, PathStatistics.PATH_FRAME, // + PathStatistics.PATH_CONTRACTION, PathStatistics.PATH_LENGTH, + PathStatistics.PATH_EXT_ANGLE_XY, PathStatistics.PATH_EXT_ANGLE_XZ, PathStatistics.PATH_EXT_ANGLE_ZY,// + PathStatistics.PATH_EXT_ANGLE_REL_XY, PathStatistics.PATH_EXT_ANGLE_REL_XZ, PathStatistics.PATH_EXT_ANGLE_REL_ZY,// + PathStatistics.PATH_ORDER, PathStatistics.PATH_N_SPINES, PathStatistics.PATH_SPINE_DENSITY, // + PathStatistics.N_BRANCH_POINTS, PathStatistics.PATH_MEAN_RADIUS, // + PathStatistics.PATH_SURFACE_AREA, PathStatistics.N_PATH_NODES, PathStatistics.PATH_VOLUME); Collections.sort(metrics); Arrays.asList("xAxisMetric", "yAxisMetric1", "yAxisMetric2", "yAxisMetric3", "yAxisMetric4").forEach(m -> { getInfo().getMutableInput(m, String.class).setChoices(metrics); @@ -136,7 +136,7 @@ public void run() { return; } paths.forEach(p -> { - final PathAnalyzer pa = new PathAnalyzer(p); + final PathStatistics pa = new PathStatistics(p); xValues.add(pa.getMetric(xAxisMetric, p).doubleValue()); if (y1Values != null) y1Values.add(pa.getMetric(yAxisMetric1, p).doubleValue()); diff --git a/src/main/java/sc/fiji/snt/plugin/PathTimeAnalysisCmd.java b/src/main/java/sc/fiji/snt/plugin/PathTimeAnalysisCmd.java index 8ba7727a..6609ce50 100644 --- a/src/main/java/sc/fiji/snt/plugin/PathTimeAnalysisCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/PathTimeAnalysisCmd.java @@ -51,7 +51,7 @@ import sc.fiji.snt.SNTService; import sc.fiji.snt.Tree; import sc.fiji.snt.analysis.MultiTreeStatistics; -import sc.fiji.snt.analysis.PathAnalyzer; +import sc.fiji.snt.analysis.PathStatistics; import sc.fiji.snt.analysis.SNTChart; import sc.fiji.snt.analysis.SNTTable; import sc.fiji.snt.analysis.TreeStatistics; @@ -140,7 +140,7 @@ private void runNonMatchedAnalysis() { ArrayList upperStdDevValues = (includeSDevSeries) ? new ArrayList<>(map.size()) : null; final String metric = getMasurementChoiceMetric(); map.forEach((frame, list) -> { - final PathAnalyzer pa = new PathAnalyzer(list, String.valueOf(frame)); + final PathStatistics pa = new PathStatistics(list, String.valueOf(frame)); xValues.add((double) frame); if (includeSDevSeries) { SummaryStatistics stats = pa.getSummaryStats(metric); @@ -241,7 +241,7 @@ private void runMatchedAnalysis(final boolean ignoreSinglePoints) { final ArrayList xValues = new ArrayList<>(groupMap.size()); final ArrayList yValues = new ArrayList<>(groupMap.size()); groupMap.forEach((frame, path) -> { - final PathAnalyzer pa = new PathAnalyzer(Collections.singletonList(path), String.valueOf(frame)); + final PathStatistics pa = new PathStatistics(Collections.singletonList(path), String.valueOf(frame)); yValues.add(pa.getMetric(metric).doubleValue()); xValues.add((double) frame); }); diff --git a/src/main/java/sc/fiji/snt/plugin/ROIExporterCmd.java b/src/main/java/sc/fiji/snt/plugin/ROIExporterCmd.java index 055e227d..7713b9fb 100644 --- a/src/main/java/sc/fiji/snt/plugin/ROIExporterCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/ROIExporterCmd.java @@ -95,22 +95,18 @@ public class ROIExporterCmd implements Command { @Override public void run() { - final RoiConverter converter = (imp == null) ? new RoiConverter(tree) : new RoiConverter(tree, imp); - if (converter.getParsedTree().isEmpty()) { - warnUser("None of the input paths could be converted to ROIs. See Console for details."); + if (tree.isEmpty()) { + warnUser("None of input paths is valid."); return; } + final RoiConverter converter = (imp == null) ? new RoiConverter(tree) : new RoiConverter(tree, imp); logService.info("Converting paths to ROIs..."); statusService.showStatus("Converting paths to ROIs..."); if (imp == null) { warn("Since no valid image data exists C,Z,T positions of ROIs may not ne set properly."); warn("If Path(s) are associated with a multi-dimensional image, you may need to load it before conversion"); } - final int skippedPaths = tree.size() - converter.getParsedTree().size(); - if (skippedPaths > 0) - warn("" + skippedPaths + " were rejected and will not be converted"); - converter.useSWCcolors(useSWCcolors); converter.setStrokeWidth((avgWidth) ? -1 : 0); Overlay overlay = new Overlay(); diff --git a/src/main/java/sc/fiji/snt/plugin/ShollAnalysisBulkTreeCmd.java b/src/main/java/sc/fiji/snt/plugin/ShollAnalysisBulkTreeCmd.java index 4d060276..f64351ef 100644 --- a/src/main/java/sc/fiji/snt/plugin/ShollAnalysisBulkTreeCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/ShollAnalysisBulkTreeCmd.java @@ -43,7 +43,7 @@ import net.imagej.ImageJ; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.sholl.Profile; import sc.fiji.snt.analysis.sholl.gui.ShollPlot; import sc.fiji.snt.analysis.sholl.gui.ShollTable; @@ -424,10 +424,10 @@ public void run() { int primaryBranches; try { logger.info(TREE_LABEL + " Retrieving primary branches..."); - primaryBranches = new TreeAnalyzer(tree).getPrimaryBranches().size(); + primaryBranches = new TreeStatistics(tree).getPrimaryBranches().size(); } catch (IllegalArgumentException exc) { logger.info(TREE_LABEL + " Failed. Defaulting to primary paths."); - primaryBranches = new TreeAnalyzer(tree).getPrimaryPaths().size(); + primaryBranches = new TreeStatistics(tree).getPrimaryPaths().size(); } lStats.setPrimaryBranches(primaryBranches); diff --git a/src/main/java/sc/fiji/snt/plugin/ShollAnalysisTreeCmd.java b/src/main/java/sc/fiji/snt/plugin/ShollAnalysisTreeCmd.java index f0621c00..7a24141b 100644 --- a/src/main/java/sc/fiji/snt/plugin/ShollAnalysisTreeCmd.java +++ b/src/main/java/sc/fiji/snt/plugin/ShollAnalysisTreeCmd.java @@ -62,8 +62,8 @@ import ij.ImagePlus; import ij.gui.Overlay; import sc.fiji.snt.SNT; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.analysis.TreeColorMapper; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.sholl.Profile; import sc.fiji.snt.analysis.sholl.ProfileEntry; import sc.fiji.snt.analysis.sholl.ShollUtils; @@ -731,10 +731,10 @@ public void runAnalysis() { int primaryBranches; try { logger.debug("Retriving primary branches..."); - primaryBranches = new TreeAnalyzer(tree).getPrimaryBranches().size(); + primaryBranches = new TreeStatistics(tree).getPrimaryBranches().size(); } catch (IllegalArgumentException exc) { logger.debug("Failure... Structure is not a graph. Defaulting to primary paths"); - primaryBranches = new TreeAnalyzer(tree).getPrimaryPaths().size(); + primaryBranches = new TreeStatistics(tree).getPrimaryPaths().size(); } lStats.setPrimaryBranches(primaryBranches); diff --git a/src/main/java/sc/fiji/snt/viewer/Viewer3D.java b/src/main/java/sc/fiji/snt/viewer/Viewer3D.java index 8cafca49..0cafef4b 100644 --- a/src/main/java/sc/fiji/snt/viewer/Viewer3D.java +++ b/src/main/java/sc/fiji/snt/viewer/Viewer3D.java @@ -161,7 +161,6 @@ import sc.fiji.snt.TreeProperties; import sc.fiji.snt.analysis.MultiTreeColorMapper; import sc.fiji.snt.analysis.SNTTable; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.analysis.TreeColorMapper; import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.analysis.graph.DirectedWeightedGraph; @@ -5398,10 +5397,10 @@ private JPopupMenu annotationsMenu() { } Annotation3D annot; if (choice.startsWith("Branch")) - annot = annotateSurface(new TreeAnalyzer(tree).getBranchPoints(), + annot = annotateSurface(new TreeStatistics(tree).getBranchPoints(), tree.getLabel() + " [BPs surface]", false); else - annot = annotateSurface(new TreeAnalyzer(tree).getTips(), + annot = annotateSurface(new TreeStatistics(tree).getTips(), tree.getLabel() + " [Tips surface]", false); final ColorRGB color = tree.getColor(); if (color != null) diff --git a/src/main/resources/script_templates/Neuroanatomy/Analysis/Get_Branch_Points_in_Brain_Compartment_Demo.groovy b/src/main/resources/script_templates/Neuroanatomy/Analysis/Get_Branch_Points_in_Brain_Compartment_Demo.groovy index 888cbe51..f44c0f62 100644 --- a/src/main/resources/script_templates/Neuroanatomy/Analysis/Get_Branch_Points_in_Brain_Compartment_Demo.groovy +++ b/src/main/resources/script_templates/Neuroanatomy/Analysis/Get_Branch_Points_in_Brain_Compartment_Demo.groovy @@ -116,12 +116,12 @@ def getFilteredBranchPoints(loader) { println(" Id matches soma location requirements!") - // Retrieve the axonal arbor as a Tree object. Instantiate a TreeAnalyzer + // Retrieve the axonal arbor as a Tree object. Instantiate TreeStatistics // so that we can conveniently 1) access all of the axonal branch points, // and 2) filter them by annotated compartment axonalTree = loader.getTree("axon") - analyzer = new TreeAnalyzer(axonalTree) - branchPoints = analyzer.getBranchPoints(projCompartment) + axonalStats = new TreeStatistics(axonalTree) + branchPoints = axonalStats.getBranchPoints(projCompartment) println(" Found ${branchPoints.size()} match(es)") return branchPoints diff --git a/src/test/java/sc/fiji/snt/IOTest.java b/src/test/java/sc/fiji/snt/IOTest.java index 8479f7a8..cc05da2b 100644 --- a/src/test/java/sc/fiji/snt/IOTest.java +++ b/src/test/java/sc/fiji/snt/IOTest.java @@ -34,7 +34,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.util.PointInImage; /** @@ -61,8 +61,8 @@ public void testTreeIO() { trees.forEach(tree -> { final List nodes = tree.getNodes(); assertEquals(nodes.size(), tree.getNodesAsSWCPoints().size()); - final double cableLength = new TreeAnalyzer(tree).getCableLength(); - final Set bps = new TreeAnalyzer(tree).getBranchPoints(); + final double cableLength = new TreeStatistics(tree).getCableLength(); + final Set bps = new TreeStatistics(tree).getBranchPoints(); try { // SWC I/O @@ -72,8 +72,8 @@ public void testTreeIO() { assertFalse("Reading file " + swcPath, swcTree.isEmpty()); // Did tree change when saving to SWC? - assertEquals(bps, new TreeAnalyzer(swcTree).getBranchPoints()); - assertEquals(cableLength, new TreeAnalyzer(swcTree).getCableLength(), precision); + assertEquals(bps, new TreeStatistics(swcTree).getBranchPoints()); + assertEquals(cableLength, new TreeStatistics(swcTree).getCableLength(), precision); // TRACES I/O final String tracesPath = folder.newFile(tree.getLabel() + ".traces").getAbsolutePath(); @@ -82,8 +82,8 @@ public void testTreeIO() { assertFalse("Reading file " + tracesPath, tracesTree.isEmpty()); // Did tree change when saving to TRACES? - assertEquals(bps, new TreeAnalyzer(tracesTree).getBranchPoints()); - assertEquals(cableLength, new TreeAnalyzer(tracesTree).getCableLength(), precision); + assertEquals(bps, new TreeStatistics(tracesTree).getBranchPoints()); + assertEquals(cableLength, new TreeStatistics(tracesTree).getCableLength(), precision); } catch (final IOException e) { e.printStackTrace(); } diff --git a/src/test/java/sc/fiji/snt/MLTreeTest.java b/src/test/java/sc/fiji/snt/MLTreeTest.java index 0d390b93..b6d87b02 100644 --- a/src/test/java/sc/fiji/snt/MLTreeTest.java +++ b/src/test/java/sc/fiji/snt/MLTreeTest.java @@ -28,11 +28,11 @@ import org.junit.Before; import org.junit.Test; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.io.MouseLightLoader; /** - * Tests for {@link TreeAnalyzer} and geometric transformations of {@link Tree}s + * Tests for {@link TreeStatistics} and geometric transformations of {@link Tree}s * * @author Tiago Ferreira */ diff --git a/src/test/java/sc/fiji/snt/PersistenceAnalyzerTest.java b/src/test/java/sc/fiji/snt/PersistenceAnalyzerTest.java index f9390c66..2160cec3 100644 --- a/src/test/java/sc/fiji/snt/PersistenceAnalyzerTest.java +++ b/src/test/java/sc/fiji/snt/PersistenceAnalyzerTest.java @@ -32,7 +32,7 @@ import org.junit.Test; import sc.fiji.snt.analysis.PersistenceAnalyzer; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.util.SWCPoint; /** @@ -44,13 +44,13 @@ public class PersistenceAnalyzerTest { private Tree tree; private PersistenceAnalyzer pAnalyzer; private final List allDescriptors = PersistenceAnalyzer.getDescriptors(); - private TreeAnalyzer tAnalyzer; + private TreeStatistics tAnalyzer; @Before public void setUp() throws Exception { tree = new SNTService().demoTrees().get(0); pAnalyzer = new PersistenceAnalyzer(tree); - tAnalyzer = new TreeAnalyzer(tree); + tAnalyzer = new TreeStatistics(tree); assumeNotNull(tree); } diff --git a/src/test/java/sc/fiji/snt/SkeletonConverterTest.java b/src/test/java/sc/fiji/snt/SkeletonConverterTest.java index 8127cce6..afa411a0 100644 --- a/src/test/java/sc/fiji/snt/SkeletonConverterTest.java +++ b/src/test/java/sc/fiji/snt/SkeletonConverterTest.java @@ -26,7 +26,7 @@ import org.junit.Before; import org.junit.Test; import sc.fiji.snt.analysis.SkeletonConverter; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import java.util.List; @@ -59,8 +59,8 @@ public void testConverter() { final SkeletonConverter converter = new SkeletonConverter(imp, false); final List skelTrees = converter.getTrees(); final Tree tree = skelTrees.get(0); - final TreeAnalyzer skelAnalyzer = new TreeAnalyzer(tree); - final TreeAnalyzer demoAnalyzer = new TreeAnalyzer(demoTree); + final TreeStatistics skelAnalyzer = new TreeStatistics(tree); + final TreeStatistics demoAnalyzer = new TreeStatistics(demoTree); assertEquals("# Trees", 1, skelTrees.size()); assertEquals("# Paths", demoAnalyzer.getNPaths(), skelAnalyzer.getNPaths()); assertEquals("# Branch points", demoAnalyzer.getBranchPoints().size(), skelAnalyzer.getBranchPoints().size()); diff --git a/src/test/java/sc/fiji/snt/TreeSkeletonTest.java b/src/test/java/sc/fiji/snt/TreeSkeletonTest.java index 9ad099bb..37c324e4 100644 --- a/src/test/java/sc/fiji/snt/TreeSkeletonTest.java +++ b/src/test/java/sc/fiji/snt/TreeSkeletonTest.java @@ -35,7 +35,7 @@ import ij.ImagePlus; import sc.fiji.analyzeSkeleton.AnalyzeSkeleton_; import sc.fiji.analyzeSkeleton.SkeletonResult; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.util.BoundingBox; /** @@ -80,7 +80,7 @@ public void testSkeletonizer() { final AnalyzeSkeleton_ skAnalyzer = new AnalyzeSkeleton_(); skAnalyzer.setup("", imp); final SkeletonResult skResult = skAnalyzer.run(); - final TreeAnalyzer analyzer = new TreeAnalyzer(tree); + final TreeStatistics analyzer = new TreeStatistics(tree); assertTrue("Match # Trees", 1 == skResult.getNumOfTrees()); if (!"AA0002".equals(tree.getLabel())) { // TODO: Assess failure for AA0002.swc diff --git a/src/test/java/sc/fiji/snt/TreeAnalyzerTest.java b/src/test/java/sc/fiji/snt/TreeStatisticsATest.java similarity index 62% rename from src/test/java/sc/fiji/snt/TreeAnalyzerTest.java rename to src/test/java/sc/fiji/snt/TreeStatisticsATest.java index 012a1c41..ec6ee405 100644 --- a/src/test/java/sc/fiji/snt/TreeAnalyzerTest.java +++ b/src/test/java/sc/fiji/snt/TreeStatisticsATest.java @@ -23,39 +23,40 @@ package sc.fiji.snt; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeNotNull; import org.junit.Before; import org.junit.Test; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; /** - * Tests for {@link TreeAnalyzer} and geometric transformations of {@link Tree}s + * Tests for {@link TreeStatistics} and geometric transformations of {@link Tree}s * * @author Tiago Ferreira */ -public class TreeAnalyzerTest { +public class TreeStatisticsATest { private final double precision = 0.0001; private Tree tree; - private TreeAnalyzer analyzer; + private TreeStatistics analyzer; @Before public void setUp() throws Exception { tree = new SNTService().demoTree("fractal"); - analyzer = new TreeAnalyzer(tree); + analyzer = new TreeStatistics(tree); assumeNotNull(tree); } @Test public void testAnalyzer() { - assertTrue("# Paths = 16", analyzer.getNPaths() == 16); - assertTrue("# Branch points = 15", analyzer.getBranchPoints().size() == 15); - assertTrue("# Tips = 16", analyzer.getTips().size() == 16); - assertTrue("# I paths = 1", analyzer.getPrimaryPaths().size() == 1); - assertTrue("# Highest path order = 5", analyzer.getHighestPathOrder() == 5); + assertEquals("# Paths = 16", 16, analyzer.getNPaths()); + assertEquals("# Branch points (tree) = 15", 15, analyzer.getBranchPoints().size()); + assertEquals("# Branch points (graph) = 15", 15, tree.getGraph().getBPs().size()); + assertEquals("# Tips (tree) = 16", 16, analyzer.getTips().size()); + assertEquals("# Tips (graph) = 16", 16, tree.getGraph().getTips().size()); + assertEquals("# I paths = 1", 1, analyzer.getPrimaryPaths().size()); + assertEquals("# Highest path order = 5", 5, analyzer.getHighestPathOrder()); final double cableLength = analyzer.getCableLength(); final double primaryLength = analyzer.getPrimaryLength(); final double terminalLength = analyzer.getTerminalLength(); @@ -68,12 +69,12 @@ public void testAnalyzer() { assertEquals("Sum length of I branches", 51.0000, primaryLength, precision); assertEquals("Sum length of terminal paths", 153.2965, terminalLength, precision); assertEquals("Avg branch length", 18.3659, avgBranchLength, precision); - assertTrue("Strahler number: 5", analyzer.getStrahlerNumber() == 5); - assertTrue("Strahler bif. ratio: 2", analyzer.getStrahlerBifurcationRatio() == 2); - assertTrue("N Branches: 31", analyzer.getNBranches() == 31); - assertTrue("Width = 116.0", analyzer.getWidth() == 116d); - assertTrue("Height = 145.0", analyzer.getHeight() == 145d); - assertTrue("Depth = 0.0", analyzer.getDepth() == 0d); + assertEquals("Strahler number: 5", 5, analyzer.getStrahlerNumber()); + assertEquals("Strahler bif. ratio: 2", 2, analyzer.getStrahlerBifurcationRatio(), 0.0); + assertEquals("N Branches: 31", 31, analyzer.getNBranches()); + assertEquals("Width = 116.0", 116d, analyzer.getWidth(), 0.0); + assertEquals("Height = 145.0", 145d, analyzer.getHeight(), 0.0); + assertEquals("Depth = 0.0", 0d, analyzer.getDepth(), 0.0); final double avgContraction = analyzer.getAvgContraction(); assertEquals("Avg contraction", 0.9628, avgContraction, precision); final double avgRemoteBifAngle = analyzer.getAvgRemoteBifAngle(); @@ -86,10 +87,10 @@ public void testAnalyzer() { // Scaling tests for (double scaleFactor : new double[] { .25d, 1d, 2d}) { tree.scale(scaleFactor, scaleFactor, scaleFactor); - final TreeAnalyzer scaledAnalyzer = new TreeAnalyzer(tree); - assertTrue("Scaling: Equal # Tips", nTips == scaledAnalyzer.getTips().size()); - assertTrue("Scaling: Equal # Branch points", nBPs == scaledAnalyzer.getBranchPoints().size()); - assertTrue("Scaling: Equal # Branches", nBranches == scaledAnalyzer.getNBranches()); + final TreeStatistics scaledAnalyzer = new TreeStatistics(tree); + assertEquals("Scaling: Equal # Tips", nTips, scaledAnalyzer.getTips().size()); + assertEquals("Scaling: Equal # Branch points", nBPs, scaledAnalyzer.getBranchPoints().size()); + assertEquals("Scaling: Equal # Branches", nBranches, scaledAnalyzer.getNBranches()); assertEquals("Scaling: Cable length", cableLength * scaleFactor, scaledAnalyzer.getCableLength(), precision); tree.scale(1 / scaleFactor, 1 / scaleFactor, 1 / scaleFactor); } @@ -98,9 +99,9 @@ public void testAnalyzer() { final double angle = 33.3; for (final int axis : new int[] {Tree.X_AXIS, Tree.Y_AXIS, Tree.Z_AXIS}) { tree.rotate(axis, angle); - final TreeAnalyzer rotatedAnalyzer = new TreeAnalyzer(tree); - assertTrue("Rotation: Equal # Tips", analyzer.getTips().size() == rotatedAnalyzer.getTips().size()); - assertTrue("Rotation: Equal # Branch points", analyzer.getBranchPoints().size() == rotatedAnalyzer.getBranchPoints().size()); + final TreeStatistics rotatedAnalyzer = new TreeStatistics(tree); + assertEquals("Rotation: Equal # Tips", analyzer.getTips().size(), rotatedAnalyzer.getTips().size()); + assertEquals("Rotation: Equal # Branch points", analyzer.getBranchPoints().size(), rotatedAnalyzer.getBranchPoints().size()); assertEquals("Rotation: Cable length", cableLength, rotatedAnalyzer.getCableLength(), precision); tree.rotate(axis, -angle); } diff --git a/src/test/java/sc/fiji/snt/TreeStatisticsTest.java b/src/test/java/sc/fiji/snt/TreeStatisticsBTest.java similarity index 98% rename from src/test/java/sc/fiji/snt/TreeStatisticsTest.java rename to src/test/java/sc/fiji/snt/TreeStatisticsBTest.java index 24c80c6a..4a292d61 100644 --- a/src/test/java/sc/fiji/snt/TreeStatisticsTest.java +++ b/src/test/java/sc/fiji/snt/TreeStatisticsBTest.java @@ -41,7 +41,7 @@ /** * Tests for {@link TreeStatistics} */ -public class TreeStatisticsTest { +public class TreeStatisticsBTest { private final double precision = 0.0001; private Tree tree; diff --git a/src/test/java/sc/fiji/snt/analysis/graph/DirectedWeightedGraphTest.java b/src/test/java/sc/fiji/snt/analysis/graph/DirectedWeightedGraphTest.java index 5f0d12af..82b32dfa 100644 --- a/src/test/java/sc/fiji/snt/analysis/graph/DirectedWeightedGraphTest.java +++ b/src/test/java/sc/fiji/snt/analysis/graph/DirectedWeightedGraphTest.java @@ -39,7 +39,7 @@ import sc.fiji.snt.Path; import sc.fiji.snt.SNTService; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.TreeAnalyzer; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.util.PointInImage; import sc.fiji.snt.util.SWCPoint; @@ -53,14 +53,14 @@ public class DirectedWeightedGraphTest { private final double precision = 1e-6; private Tree tree; - private TreeAnalyzer analyzer; + private TreeStatistics analyzer; private DirectedWeightedGraph graph; @Before public void setUp() throws Exception { // tree = new MouseLightLoader("AA0103").getTree(); tree = new SNTService().demoTrees().get(0); - analyzer = new TreeAnalyzer(tree); + analyzer = new TreeStatistics(tree); graph = tree.getGraph(); } @@ -82,7 +82,7 @@ public void testVertexEquality() { @Test public void testGraphProperties() { final int numRoots = (int) graph.vertexSet().stream().filter(v -> graph.inDegreeOf(v) == 0).count(); - // Compare measurements against TreeAnalyzer since TreeAnalyzer is compared + // Compare measurements against TreeStatistics since TreeStatistics is compared // against the hard-coded correct values assertEquals(tree.getNodes().size(), graph.vertexSet().size()); assertEquals(1, numRoots); @@ -269,7 +269,7 @@ public void testSimplifiedGraph() { final PointInImage root = tree.getRoot(); final DirectedWeightedGraph simplifiedGraph = tree.getGraph(true); final Tree simplifiedTree = simplifiedGraph.getTree(true); - final TreeAnalyzer simplifiedAnalyzer = new TreeAnalyzer(simplifiedTree); + final TreeStatistics simplifiedAnalyzer = new TreeStatistics(simplifiedTree); // The demo tree root is also a branch point, so exclude it from the count assertEquals(numBranchPoints + numTips, simplifiedGraph.vertexSet().size()); assertEquals(numBranchPoints, simplifiedGraph.getBPs().size()); @@ -286,7 +286,7 @@ public void testSimplifiedGraph() { @Test public void testGraphToTree() { final Tree newTree = graph.getTree(true); - final TreeAnalyzer newAnalyzer = new TreeAnalyzer(newTree); + final TreeStatistics newAnalyzer = new TreeStatistics(newTree); assertEquals(tree.getNodes().size(), newTree.getNodes().size()); assertEquals(analyzer.getCableLength(), newAnalyzer.getCableLength(), precision); assertEquals(analyzer.getNBranches(), newAnalyzer.getNBranches()); @@ -332,7 +332,7 @@ public void testModificationAndConversionToTree() { graph.addVertex(newRoot); graph.addEdge(newRoot, oldRoot); final Tree changedTree = graph.getTree(true); - final TreeAnalyzer changedAnalyzer = new TreeAnalyzer(changedTree); + final TreeStatistics changedAnalyzer = new TreeStatistics(changedTree); final PointInImage newTreeRoot = changedTree.getRoot(); assertTrue(newTreeRoot.getX() == 0.5 && newTreeRoot.getY() == 0.5 && newTreeRoot.getZ() == 0.5); diff --git a/src/test/java/sc/fiji/snt/demo/RecViewerDemo.java b/src/test/java/sc/fiji/snt/demo/RecViewerDemo.java index 1ef139e1..fa8b679f 100644 --- a/src/test/java/sc/fiji/snt/demo/RecViewerDemo.java +++ b/src/test/java/sc/fiji/snt/demo/RecViewerDemo.java @@ -28,8 +28,8 @@ import net.imagej.display.ColorTables; import sc.fiji.snt.SNTUtils; import sc.fiji.snt.Tree; -import sc.fiji.snt.analysis.TreeAnalyzer; import sc.fiji.snt.analysis.TreeColorMapper; +import sc.fiji.snt.analysis.TreeStatistics; import sc.fiji.snt.annotation.AllenUtils; import sc.fiji.snt.io.MouseLightLoader; import sc.fiji.snt.util.SNTPoint; @@ -69,7 +69,7 @@ public static void main(final String[] args) { final Tree aa1044 = loader.getTree("axon"); if (aa1044 != null) { // server is online and reachable viewer.addTree(aa1044); - viewer.annotateSurface(new TreeAnalyzer(aa1044).getTips(), "Convex Hull Tips", true); + viewer.annotateSurface(new TreeStatistics(aa1044).getTips(), "Convex Hull Tips", true); final TreeColorMapper mapper = new TreeColorMapper(); mapper.map(aa1044, TreeColorMapper.PATH_ORDER, ColorTables.ICE); viewer.rebuild(aa1044);