Skip to content

Commit d1b3e05

Browse files
committed
Add multiple checks to curation assistant...
Including:, BoundaryProximity, BundledPaths, IntensityValley, InterForkDistance, TerminalNearAncestor, UncertainTerminal, and ZExtentRatio While at it: - Fix bin calculation - Adjust parameter labels - Officialize BundleDetector as a standalone class
1 parent 95b13b7 commit d1b3e05

11 files changed

Lines changed: 1754 additions & 243 deletions

‎src/main/java/sc/fiji/snt/CurationManager.java‎

Lines changed: 481 additions & 176 deletions
Large diffs are not rendered by default.

‎src/main/java/sc/fiji/snt/PathManagerUI.java‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,7 +2932,7 @@ private class MultiPathActionListener implements ActionListener {
29322932
private static final String APPEND_ALL_CHILDREN_CMD = "Append All Children To Selection";
29332933
private static final String APPEND_DIRECT_CHILDREN_CMD = "Append Direct Children To Selection";
29342934
private static final String ASSIGN_DISTINCT_COLORS = "Distinct Colors";
2935-
private static final String ASSIGN_CUSTOM_COLOR = "Other...";
2935+
private static final String ASSIGN_CUSTOM_COLOR = "Other... "; // must be unique: space to differentiate CUSTOM_TAG_CMD
29362936
private static final String COLORS_MENU = "Color";
29372937
private static final String DELETE_CMD = "Delete...";
29382938
private static final String AUTO_CONNECT_CMD = "Auto-connect...";
@@ -4202,7 +4202,7 @@ private ConnectionAnalysis analyzeConnection(final Path p1, final Path p2) {
42024202
double minDist = p1StartToP2;
42034203
Path suggestedParent = p2;
42044204
Path suggestedChild = p1;
4205-
boolean childStartIsCloser = true;
4205+
boolean childStartIsCloser;
42064206

42074207
if (p1EndToP2 < minDist) {
42084208
minDist = p1EndToP2;
@@ -5994,9 +5994,7 @@ private void showColorCustomizationDialog() {
59945994
tagsMap.keySet().forEach( tagName -> saveTagColor(tagName, null));
59955995
tagsMap.clear();
59965996
initializeTagColors();
5997-
colorButtons.forEach( (tagName, button) -> {
5998-
button.setSelectedColor(tagsMap.get(tagName), true);
5999-
});
5997+
colorButtons.forEach( (tagName, button) -> button.setSelectedColor(tagsMap.get(tagName), true));
60005998
});
60015999
panel.add(resetButton, c);
60026000

‎src/main/java/sc/fiji/snt/analysis/AnalysisUtils.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,11 @@ void compute() {
870870
} else {
871871
nBins = (int) Math.ceil((max - min) / binWidth);
872872
}
873+
// Cap nBins to protect against exaggeration when the distribution has a heavy tail or an extreme
874+
// outlier: Freedman-Diaconis picks the bin width robustly from the IQR, but the bin count uses
875+
// (max - min), which is not robust. A single outlier can push nBins into the tens of thousands
876+
// The cap (1.5x the formal square-root rule) keeps the count proportional to the data
877+
nBins = Math.min(nBins, (int) Math.ceil(1.5 * Math.sqrt(n)));
873878
nBins = Math.max(1, nBins);
874879
}
875880
}

‎src/main/java/sc/fiji/snt/analysis/PathProfiler.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public void run() {
390390
private List<Integer> getChannels() {
391391
if (channelString == null || channelString.trim().isEmpty() || "all".equalsIgnoreCase(channelString.trim()))
392392
return getAllChannels();
393-
final List<String> stringChannels = new ArrayList<>(Arrays.asList(channelString.split("\\s*(,|\\s)\\s*")));
393+
final List<String> stringChannels = new ArrayList<>(Arrays.asList(channelString.split("\\s*([,\\s])\\s*")));
394394
final List<Integer> validChannels = new ArrayList<>();
395395
for (final String chString : stringChannels) {
396396
try {
@@ -531,7 +531,7 @@ private Map<String, double[]> getValuesAsArray(final Path p, final int channel)
531531
public int[] findMaxima(final Path path, final int channel) {
532532
final double[] profile = getValues(path, channel).get(PathProfiler.Y_VALUES).stream().mapToDouble(d -> d).toArray();
533533
final SummaryStatistics stats = new SummaryStatistics();
534-
Arrays.stream(profile).forEach(v -> stats.addValue(v));
534+
Arrays.stream(profile).forEach(stats::addValue);
535535
return MaximumFinder.findMaxima(profile, stats.getStandardDeviation(), false);
536536
}
537537

@@ -548,7 +548,7 @@ public int[] findMaxima(final Path path, final int channel) {
548548
public int[] findMinima(final Path path, final int channel) {
549549
final double[] profile = getValues(path, channel).get(PathProfiler.Y_VALUES).stream().mapToDouble(d -> d).toArray();
550550
final SummaryStatistics stats = new SummaryStatistics();
551-
Arrays.stream(profile).forEach(v -> stats.addValue(v));
551+
Arrays.stream(profile).forEach(stats::addValue);
552552
return MaximumFinder.findMinima(profile, stats.getStandardDeviation(), false);
553553
}
554554

‎src/main/java/sc/fiji/snt/analysis/curation/CurationHistograms.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,8 @@ private static void showHistogram(final String checkName, final PlausibilityChec
208208
new GuiUtils(parent).centeredMsg(msg, WordUtils.capitalizeFully(checkName));
209209
return;
210210
}
211-
212-
final boolean polar = checkName != null && (checkName.toLowerCase().contains("angle")
213-
|| checkName.toLowerCase().contains("°") || checkName.toLowerCase().contains("direction"));
211+
final boolean polar = m.unit() != null && m.unit().contains("°") ||
212+
checkName != null && (checkName.toLowerCase().contains("angle") || checkName.toLowerCase().contains("direction"));
214213
final SNTChart chart = (polar) ?
215214
SNTChart.getPolarHistogram(m.values(), m.metric(), m.unit())
216215
: SNTChart.getHistogram(m.values(), m.metric(), m.unit());

‎src/main/java/sc/fiji/snt/analysis/curation/PlausibilityCalibrator.java‎

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void applyTo(final PlausibilityMonitor monitor) {
109109
final PlausibilityCheck.RadiusContinuity chk = monitor.getLiveCheck(PlausibilityCheck.RadiusContinuity.class);
110110
if (chk != null) chk.setMaxRatio(s.computedValue);
111111
}
112-
case "Terminal branch length" -> {
112+
case "Terminal path length" -> {
113113
final PlausibilityCheck.TerminalBranchLength chk = monitor.getLiveCheck(PlausibilityCheck.TerminalBranchLength.class);
114114
if (chk != null) chk.setMinLengthUm(s.computedValue);
115115
}
@@ -125,11 +125,11 @@ public void applyTo(final PlausibilityMonitor monitor) {
125125
final PlausibilityCheck.PathOverlap chk = monitor.getDeepCheck(PlausibilityCheck.PathOverlap.class);
126126
if (chk != null) chk.setProximityUm(s.computedValue);
127127
}
128-
case "Radius jumps" -> {
128+
case "Thickness jumps" -> {
129129
final PlausibilityCheck.RadiusJumps chk = monitor.getDeepCheck(PlausibilityCheck.RadiusJumps.class);
130130
if (chk != null) chk.setMaxJumpRatio(s.computedValue);
131131
}
132-
case "Radius monotonicity" -> {
132+
case "Thickness inversions" -> {
133133
final PlausibilityCheck.RadiusMonotonicity chk = monitor.getDeepCheck(PlausibilityCheck.RadiusMonotonicity.class);
134134
if (chk != null) chk.setMinIncreasingRun((int) Math.round(s.computedValue));
135135
}
@@ -313,7 +313,7 @@ private void calibrateTerminalBranchLength(final CalibrationResult result) {
313313
stats.addValue(path.getLength());
314314
}
315315
}
316-
addSummary(result, "Terminal branch length", "Min. length",
316+
addSummary(result, "Terminal path length", "Min. length",
317317
stats, lowerPercentile);
318318
}
319319

@@ -395,7 +395,7 @@ private void calibrateRadiusJumps(final CalibrationResult result) {
395395
}
396396
}
397397
}
398-
addSummary(result, "Radius jumps", "Max. jump ratio",
398+
addSummary(result, "Thickness jumps", "Max. jump ratio",
399399
stats, upperPercentile);
400400
}
401401

@@ -418,7 +418,7 @@ private void calibrateRadiusMonotonicity(final CalibrationResult result) {
418418
if (runLength > 0) stats.addValue(runLength);
419419
}
420420
}
421-
addSummary(result, "Radius monotonicity", "Max. increasing run",
421+
addSummary(result, "Thickness inversions", "Max. increasing run",
422422
stats, upperPercentile);
423423
}
424424

@@ -543,6 +543,11 @@ public static void save(final PlausibilityMonitor monitor, final File file,
543543
if (cr != null) {
544544
entries.put("constantRadii.enabled", String.valueOf(cr.isEnabled()));
545545
}
546+
final PlausibilityCheck.InterForkDistance ifd = monitor.getLiveCheck(PlausibilityCheck.InterForkDistance.class);
547+
if (ifd != null) {
548+
entries.put("interForkDistance.minDistance", String.valueOf(ifd.getMinDistanceUm()));
549+
entries.put("interForkDistance.enabled", String.valueOf(ifd.isEnabled()));
550+
}
546551

547552
// Deep checks
548553
final PlausibilityCheck.PathOverlap po = monitor.getDeepCheck(PlausibilityCheck.PathOverlap.class);
@@ -565,6 +570,42 @@ public static void save(final PlausibilityMonitor monitor, final File file,
565570
entries.put("signalQuality.minContrast", String.valueOf(sq.getMinContrast()));
566571
entries.put("signalQuality.enabled", String.valueOf(sq.isEnabled()));
567572
}
573+
final PlausibilityCheck.UncertainTerminal ut = monitor.getDeepCheck(PlausibilityCheck.UncertainTerminal.class);
574+
if (ut != null) {
575+
entries.put("uncertainTerminal.minTipContrast", String.valueOf(ut.getMinTipContrast()));
576+
entries.put("uncertainTerminal.tailNodes", String.valueOf(ut.getTailNodes()));
577+
entries.put("uncertainTerminal.enabled", String.valueOf(ut.isEnabled()));
578+
}
579+
final PlausibilityCheck.IntensityValley iv = monitor.getDeepCheck(PlausibilityCheck.IntensityValley.class);
580+
if (iv != null) {
581+
// Property key keeps the class-name root (intensityValley) for stability
582+
// even though the user-facing label was renamed to "Path signal quality dips".
583+
entries.put("intensityValley.minProminence", String.valueOf(iv.getMinProminence()));
584+
entries.put("intensityValley.enabled", String.valueOf(iv.isEnabled()));
585+
}
586+
final PlausibilityCheck.TerminalNearAncestor tna = monitor.getDeepCheck(PlausibilityCheck.TerminalNearAncestor.class);
587+
if (tna != null) {
588+
entries.put("terminalNearAncestor.maxProximity", String.valueOf(tna.getMaxProximityUm()));
589+
entries.put("terminalNearAncestor.enabled", String.valueOf(tna.isEnabled()));
590+
}
591+
final PlausibilityCheck.BoundaryProximity bp = monitor.getDeepCheck(PlausibilityCheck.BoundaryProximity.class);
592+
if (bp != null) {
593+
// Off by default and not exposed in the GUI; still serialized so scripts/presets can opt in.
594+
entries.put("boundaryProximity.minVoxels", String.valueOf(bp.getMinVoxelsFromBoundary()));
595+
entries.put("boundaryProximity.enabled", String.valueOf(bp.isEnabled()));
596+
}
597+
final PlausibilityCheck.ZExtentRatio ze = monitor.getDeepCheck(PlausibilityCheck.ZExtentRatio.class);
598+
if (ze != null) {
599+
entries.put("zExtentRatio.minRatio", String.valueOf(ze.getMinRatio()));
600+
entries.put("zExtentRatio.enabled", String.valueOf(ze.isEnabled()));
601+
}
602+
final PlausibilityCheck.BundledPaths bd = monitor.getDeepCheck(PlausibilityCheck.BundledPaths.class);
603+
if (bd != null) {
604+
entries.put("bundledPaths.maxParallelAngle", String.valueOf(bd.getMaxParallelAngleDeg()));
605+
entries.put("bundledPaths.minRunNodes", String.valueOf(bd.getMinRunNodes()));
606+
entries.put("bundledPaths.proximity", String.valueOf(bd.getProximityUm()));
607+
entries.put("bundledPaths.enabled", String.valueOf(bd.isEnabled()));
608+
}
568609

569610
// Monitor-level state
570611
entries.put("monitor.enabled", String.valueOf(monitor.isEnabled()));
@@ -657,6 +698,11 @@ private static void applyProperties(final Properties props, final PlausibilityMo
657698
if (cr != null) {
658699
setEnabled(props, "constantRadii.enabled", cr::setEnabled);
659700
}
701+
final PlausibilityCheck.InterForkDistance ifd = monitor.getLiveCheck(PlausibilityCheck.InterForkDistance.class);
702+
if (ifd != null) {
703+
setDouble(props, "interForkDistance.minDistance", ifd::setMinDistanceUm);
704+
setEnabled(props, "interForkDistance.enabled", ifd::setEnabled);
705+
}
660706

661707
// Deep checks
662708
final PlausibilityCheck.PathOverlap po = monitor.getDeepCheck(PlausibilityCheck.PathOverlap.class);
@@ -679,6 +725,39 @@ private static void applyProperties(final Properties props, final PlausibilityMo
679725
setDouble(props, "signalQuality.minContrast", sq::setMinContrast);
680726
setEnabled(props, "signalQuality.enabled", sq::setEnabled);
681727
}
728+
final PlausibilityCheck.UncertainTerminal ut = monitor.getDeepCheck(PlausibilityCheck.UncertainTerminal.class);
729+
if (ut != null) {
730+
setDouble(props, "uncertainTerminal.minTipContrast", ut::setMinTipContrast);
731+
setInt(props, "uncertainTerminal.tailNodes", ut::setTailNodes);
732+
setEnabled(props, "uncertainTerminal.enabled", ut::setEnabled);
733+
}
734+
final PlausibilityCheck.IntensityValley iv = monitor.getDeepCheck(PlausibilityCheck.IntensityValley.class);
735+
if (iv != null) {
736+
setDouble(props, "intensityValley.minProminence", iv::setMinProminence);
737+
setEnabled(props, "intensityValley.enabled", iv::setEnabled);
738+
}
739+
final PlausibilityCheck.TerminalNearAncestor tna = monitor.getDeepCheck(PlausibilityCheck.TerminalNearAncestor.class);
740+
if (tna != null) {
741+
setDouble(props, "terminalNearAncestor.maxProximity", tna::setMaxProximityUm);
742+
setEnabled(props, "terminalNearAncestor.enabled", tna::setEnabled);
743+
}
744+
final PlausibilityCheck.BoundaryProximity bp = monitor.getDeepCheck(PlausibilityCheck.BoundaryProximity.class);
745+
if (bp != null) {
746+
setDouble(props, "boundaryProximity.minVoxels", bp::setMinVoxelsFromBoundary);
747+
setEnabled(props, "boundaryProximity.enabled", bp::setEnabled);
748+
}
749+
final PlausibilityCheck.ZExtentRatio ze = monitor.getDeepCheck(PlausibilityCheck.ZExtentRatio.class);
750+
if (ze != null) {
751+
setDouble(props, "zExtentRatio.minRatio", ze::setMinRatio);
752+
setEnabled(props, "zExtentRatio.enabled", ze::setEnabled);
753+
}
754+
final PlausibilityCheck.BundledPaths bd = monitor.getDeepCheck(PlausibilityCheck.BundledPaths.class);
755+
if (bd != null) {
756+
setDouble(props, "bundledPaths.maxParallelAngle", bd::setMaxParallelAngleDeg);
757+
setInt(props, "bundledPaths.minRunNodes", bd::setMinRunNodes);
758+
setDouble(props, "bundledPaths.proximity", bd::setProximityUm);
759+
setEnabled(props, "bundledPaths.enabled", bd::setEnabled);
760+
}
682761

683762
// Monitor-level state (only present in session files, not calibration presets)
684763
setEnabled(props, "monitor.enabled", monitor::setEnabled);

0 commit comments

Comments
 (0)