Skip to content

Commit cdb9cd4

Browse files
committed
fix #333
1 parent 57a870d commit cdb9cd4

11 files changed

Lines changed: 165 additions & 62 deletions

File tree

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,9 +1111,8 @@ private void redrawEditingPath(final Graphics2D g) {
11111111
// what the user sees. The editableNodeIndex lives on editingPath
11121112
// (always the main/unfitted path), so we temporarily propagate
11131113
// it to the fitted copy for rendering, then clear it afterwards.
1114-
Path drawPath = editingPath;
1115-
if (editingPath.getUseFitted() && editingPath.getFitted() != null) {
1116-
drawPath = editingPath.getFitted();
1114+
final Path drawPath = editingPath.getVersionInUse();
1115+
if (drawPath != editingPath) {
11171116
drawPath.setEditableNode(editingPath.getEditableNodeIndex());
11181117
drawPath.setEditableNodeLocked(editingPath.isEditableNodeLocked());
11191118
}
@@ -1745,8 +1744,7 @@ protected void togglePaintMode() {
17451744
// Auto-select nearest node if none is active yet (e.g., user pressed P
17461745
// right after Shift+E without moving the mouse over the path)
17471746
if (editingPath.getEditableNodeIndex() == -1) {
1748-
final Path matchPath = editingPath.getUseFitted()
1749-
? editingPath.getFitted() : editingPath;
1747+
final Path matchPath = editingPath.getVersionInUse();
17501748
int nearest = matchPath.indexNearestToCanvasPosition2D(
17511749
last_x_in_pane_precise, last_y_in_pane_precise, nodeDiameter());
17521750
if (nearest == -1) nearest = 0; // fallback to first node
@@ -1890,8 +1888,7 @@ protected void assignRadiusToEditingNode(final boolean warnOnFailure) {
18901888
private void brushMoveNodes(final Path editingPath) {
18911889
// Resolve fitted path for nearest-node lookup (no distance threshold
18921890
// so fast drags don't outrun the nodes and stall painting)
1893-
final Path matchPath = editingPath.getUseFitted()
1894-
? editingPath.getFitted() : editingPath;
1891+
final Path matchPath = editingPath.getVersionInUse();
18951892
final int centerNode = matchPath.indexNearestToCanvasPosition2D(
18961893
last_x_in_pane_precise, last_y_in_pane_precise, Double.MAX_VALUE);
18971894
if (centerNode == -1 || centerNode >= editingPath.size()) return;
@@ -1916,7 +1913,7 @@ private void brushMoveNodes(final Path editingPath) {
19161913
}
19171914

19181915
private void brushBlendRadii(final Path editingPath) {
1919-
final Path matchPath = editingPath.getUseFitted() ? editingPath.getFitted() : editingPath;
1916+
final Path matchPath = editingPath.getVersionInUse();
19201917
final int centerNode = matchPath.indexNearestToCanvasPosition2D(
19211918
last_x_in_pane_precise, last_y_in_pane_precise, Double.MAX_VALUE);
19221919
if (centerNode == -1 || centerNode >= editingPath.size()) return;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,6 +2540,17 @@ public Path getUnfitted() {
25402540
return fittedVersionOf;
25412541
}
25422542

2543+
/**
2544+
* Returns the version of this path that is in use, i.e., the one that is displayed and analyzed: its fitted
2545+
* flavor if {@link #getUseFitted()}, otherwise this path. This is the registry-free rule: for the
2546+
* registry-aware one see {@link PathAndFillManager#getVersionInUse(Path)}
2547+
*
2548+
* @return the version in use. Never null
2549+
*/
2550+
public Path getVersionInUse() {
2551+
return getUseFitted() ? fitted : this;
2552+
}
2553+
25432554
/**
25442555
* Single source of truth for the "should this fitted-version path be treated as redundant/standalone" decision
25452556
* used throughout listings, filters, and measurements.

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ public synchronized Path[] getPathsStructured() {
676676
public Collection<Tree> getTrees() {
677677
final HashMap<String, Tree> map = new HashMap<>();
678678
allPaths.forEach(p->{
679-
final Path pathToUse = (p.getUseFitted()) ? p.getFitted() : p;
679+
final Path pathToUse = getVersionInUse(p);
680680
String treeID = pathToUse.getTreeLabel();
681681
if (map.get(treeID) == null) {
682682
final Tree tree = new Tree();
@@ -704,7 +704,7 @@ public Collection<Tree> getTrees() {
704704
public Tree getTree(final String treeLabel) {
705705
final Tree tree = new Tree();
706706
allPaths.forEach(p->{
707-
final Path pathToUse = (p.getUseFitted()) ? p.getFitted() : p;
707+
final Path pathToUse = getVersionInUse(p);
708708
if (treeLabel.equals(pathToUse.getTreeLabel()))
709709
tree.add(pathToUse);
710710
});
@@ -724,7 +724,7 @@ public Tree getTree(final String treeLabel) {
724724
public Tree getTree(final int treeId) {
725725
final Tree tree = new Tree();
726726
allPaths.forEach(p->{
727-
final Path pathToUse = (p.getUseFitted()) ? p.getFitted() : p;
727+
final Path pathToUse = getVersionInUse(p);
728728
if (treeId == pathToUse.getTreeID())
729729
tree.add(pathToUse);
730730
});
@@ -1971,9 +1971,7 @@ private void addImportedPathsTo3DViewer() {
19711971
// never registered) is itself the de facto path and must still be added
19721972
if (!isDeFactoPath(p) || p.size() < 2)
19731973
continue;
1974-
Path pathToAdd;
1975-
if (p.getUseFitted()) pathToAdd = p.getFitted();
1976-
else pathToAdd = p;
1974+
final Path pathToAdd = p.getVersionInUse();
19771975
pathToAdd.addTo3DViewer(plugin.univ, SNTPrefs.deselectedPathColor(), plugin.colorImage);
19781976
}
19791977
}
@@ -2015,6 +2013,7 @@ public void endElement(final String uri, final String localName,
20152013
final Integer fittedID = fittedFields.get(p.getID());
20162014
final Integer fittedVersionOfID = fittedVersionOfFields.get(p.getID());
20172015
final Boolean useFitted = useFittedFields.get(p.getID());
2016+
boolean fitDiscarded = false;
20182017

20192018
if (startID != null) {
20202019
final Path startPath = getPathFromID(startID);
@@ -2056,6 +2055,20 @@ public void endElement(final String uri, final String localName,
20562055

20572056
pReversed.setBranchFrom(endPath, endJoinPoint);
20582057

2058+
// reversed() does not carry over the fit: discard it (and its
2059+
// registration) so that no dangling fitted links remain
2060+
if (fittedID != null) {
2061+
final Path fit = getPathFromID(fittedID);
2062+
if (fit != null) {
2063+
allPaths.remove(fit);
2064+
pathIdMap.remove(fit.getID(), fit);
2065+
pathNameMap.remove(fit.getName(), fit);
2066+
}
2067+
fitDiscarded = true;
2068+
SNTUtils.log("End-joined path " + p.getID() + " ('" + p.getName()
2069+
+ "') was fitted: fit discarded");
2070+
}
2071+
20592072
for (final Path join : new ArrayList<>(p.connectedPaths)) {
20602073
if (join.getParentPath() == p) {
20612074
final PointInImage joinPoint = join.getBranchPoint();
@@ -2065,9 +2078,16 @@ public void endElement(final String uri, final String localName,
20652078
}
20662079
} // end null-check else
20672080
}
2068-
if (fittedID != null) {
2069-
p.fitted = getPathFromID(fittedID);
2070-
p.setUseFitted(useFitted);
2081+
if (fittedID != null && !fitDiscarded) {
2082+
final Path fit = getPathFromID(fittedID);
2083+
if (fit == null) {
2084+
SNTUtils.log("Malformed traces file: fitted=" + fittedID
2085+
+ " referenced by path " + p.getID() + " ('" + p.getName()
2086+
+ "') does not exist. Fit skipped.");
2087+
} else {
2088+
p.fitted = fit;
2089+
p.setUseFitted(useFitted);
2090+
}
20712091
}
20722092
if (fittedVersionOfID != null) {
20732093
p.fittedVersionOf = getPathFromID(fittedVersionOfID);
@@ -2092,10 +2112,6 @@ else if (p != p.fittedVersionOf.getFitted())
20922112
throw new TracesFileFormatException(
20932113
"Malformed traces file: p didn't match p.fittedVersionOf.fitted");
20942114
}
2095-
else if (p.getUseFitted()) {
2096-
throw new TracesFileFormatException(
2097-
"Malformed traces file: p.useFitted was true but p.fitted was null");
2098-
}
20992115
}
21002116

21012117
// allPaths have been used to store all imported paths, including fitted ones.
@@ -3156,10 +3172,7 @@ synchronized void setPathPointsInVolume(final Collection<Path> paths,
31563172
for (final Path topologyPath : paths) {
31573173
if (pixelIntensity == -1)
31583174
actualPixelIntensity = (short) ++label;
3159-
Path p = topologyPath;
3160-
if (topologyPath.getUseFitted()) {
3161-
p = topologyPath.getFitted();
3162-
}
3175+
final Path p = topologyPath.getVersionInUse();
31633176
final int n = p.size();
31643177

31653178
final ArrayList<Bresenham3D.IntegerPoint> pointsToJoin =
@@ -3266,7 +3279,8 @@ public synchronized NodeRef nearestNodeOnPaths(final Collection<Path> paths, fin
32663279

32673280
/**
32683281
* Returns the version of a path that is in use, i.e., the one that is displayed and analyzed: its fitted
3269-
* flavor if {@link Path#getUseFitted()}, otherwise the path itself. Same rule as {@link Tree#Tree(Collection)}.
3282+
* flavor if {@link Path#getUseFitted()}, otherwise the path itself. Same rule as
3283+
* {@link Tree#Tree(Collection)}, which uses the collection itself as registry.
32703284
* A fitted copy whose original is registered with this manager is first resolved to that original (see
32713285
* {@link #isDeFactoPath(Path)}), so that both flavors of a path always resolve to the same version.
32723286
*
@@ -3276,7 +3290,7 @@ public synchronized NodeRef nearestNodeOnPaths(final Collection<Path> paths, fin
32763290
public synchronized Path getVersionInUse(final Path p) {
32773291
if (p == null) return null;
32783292
final Path base = (p.isFittedVersionOfAnotherPath() && !isDeFactoPath(p)) ? p.getUnfitted() : p;
3279-
return base.getUseFitted() ? base.getFitted() : base;
3293+
return base.getVersionInUse();
32803294
}
32813295

32823296
/**
@@ -3680,10 +3694,7 @@ public void exportToCSV(final File outputFile) throws IOException {
36803694
}
36813695
pw.print("\r\n");
36823696
for (final Path p : allPaths) {
3683-
Path pForLengthAndName = p;
3684-
if (p.getUseFitted()) {
3685-
pForLengthAndName = p.getFitted();
3686-
}
3697+
final Path pForLengthAndName = p.getVersionInUse();
36873698
if (p.fittedVersionOf != null) continue;
36883699
SNTUtils.csvQuoteAndPrint(pw, p.getID()); //col1: PathID
36893700
pw.print(",");
@@ -3766,7 +3777,7 @@ protected void update3DViewerContents() {
37663777

37673778
// If path is being rendered with its own custom color, highlight it
37683779
// somehow if is being selected
3769-
if (p.getUseFitted()) p = p.getFitted();
3780+
p = p.getVersionInUse();
37703781
if (p.content3D != null) p.content3D.setShaded(!(customColor &&
37713782
selected));
37723783

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5407,7 +5407,7 @@ private void applyZCorrection(final List<Path> selectedPaths) {
54075407
if (plugin.accessToValidImageData()) {
54085408
final double maxZ = plugin.depth - 1;
54095409
for (final Path p : selectedPaths) {
5410-
final Path pathToUse = p.getUseFitted() ? p.getFitted() : p;
5410+
final Path pathToUse = p.getVersionInUse();
54115411
for (int node = 0; node < pathToUse.size(); node++) {
54125412
final double newZ = pathToUse.getNodeWithoutChecks(node).z * factor;
54135413
if (newZ < 0 || newZ > maxZ) oobNodes++;
@@ -5420,7 +5420,7 @@ private void applyZCorrection(final List<Path> selectedPaths) {
54205420
}
54215421
}
54225422
for (final Path p : selectedPaths) {
5423-
final Path pathToUse = p.getUseFitted() ? p.getFitted() : p;
5423+
final Path pathToUse = p.getVersionInUse();
54245424
for (int node = 0; node < pathToUse.size(); node++) {
54255425
final PointInImage current = pathToUse.getNodeWithoutChecks(node);
54265426
pathToUse.moveNode(node, current.x, current.y, current.z * factor);
@@ -5678,11 +5678,7 @@ private void dowsamplePaths(final List<Path> selectedPaths) {
56785678
return;
56795679
}
56805680
for (final Path p : selectedPaths) {
5681-
Path pathToUse = p;
5682-
if (p.getUseFitted()) {
5683-
pathToUse = p.getFitted();
5684-
}
5685-
pathToUse.downsample(maxDeviation);
5681+
p.getVersionInUse().downsample(maxDeviation);
56865682
}
56875683
// Make sure that the 3D viewer and the stacks are redrawn:
56885684
if (plugin.univ != null) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,7 +2592,7 @@ else if (editing && !editingPath.isEditableNodeLocked()) {
25922592
// then activate the Z-slice of the retrieved node.
25932593
// When the fitted version is displayed, match against fitted nodes
25942594
// so the user clicks on what they see
2595-
final Path matchPath = editingPath.getUseFitted() ? editingPath.getFitted() : editingPath;
2595+
final Path matchPath = editingPath.getVersionInUse();
25962596
final int eNode = matchPath.indexNearestToCanvasPosition2D(x, y,
25972597
getXYCanvas().nodeDiameter());
25982598
if (eNode != -1) {
@@ -6368,7 +6368,7 @@ private ImagePlus captureView(final ImagePlus holdingImp, final String viewDescr
63686368
// See PathAndFillManager#isDeFactoPath(Path): only skip a fitted-version path when
63696369
// its un-fitted counterpart is also registered here to be drawn in its place
63706370
if (p == null || !pathAndFillManager.isDeFactoPath(p)) continue;
6371-
final Path drawPath = (p.getUseFitted() && p.getFitted() != null) ? p.getFitted() : p;
6371+
final Path drawPath = p.getVersionInUse();
63726372
drawPath.drawPathAsPoints(g, canvas, this);
63736373
}
63746374
// this is taken from ImagePlus.flatten()

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,8 @@ protected void drawOverlay(final Graphics2D g) {
115115

116116
if (p.fittedVersionOf != null) continue;
117117

118-
Path drawPath = p;
119-
120-
// If the path suggests using the fitted version, draw that
121-
// instead:
122-
if (p.getUseFitted()) {
123-
drawPath = p.getFitted();
124-
}
118+
// Draw the version in use (fitted if flagged)
119+
final Path drawPath = p.getVersionInUse();
125120

126121
// Selection state is always tracked on the main (unfitted) path,
127122
// not the fitted copy, so test against p rather than drawPath

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,20 @@ public Tree() {
9393
*
9494
* @param paths the Collection of paths forming this tree. Null not allowed.
9595
* Note that when a Path has been fitted and
96-
* {@link Path#getUseFitted()} is true, its fitted 'flavor' is used.
96+
* {@link Path#getUseFitted()} is true, its fitted 'flavor' is used
97+
* (see {@link Path#getVersionInUse()}). A fitted copy whose original is
98+
* also in the collection is not added twice.
9799
*/
98100
public Tree(final Collection<Path> paths) {
99101
if (paths == null) throw new IllegalArgumentException(
100102
"Cannot instantiate a new tree from a null collection");
101103
tree = new ArrayList<>(paths.size());
104+
final Set<Path> lookup = new HashSet<>(paths);
102105
for (final Path p : paths) {
103-
if (p == null) continue;
104-
Path pathToAdd;
105-
// If fitted flavor of path exists use it instead
106-
if (p.getUseFitted() && p.getFitted() != null) {
107-
pathToAdd = p.getFitted();
108-
} else {
109-
pathToAdd = p;
110-
}
111-
tree.add(pathToAdd);
106+
// A fitted copy whose original is also in paths is skipped: the
107+
// original resolves to the same version (see isDeFactoPath(Collection))
108+
if (p == null || !p.isDeFactoPath(lookup)) continue;
109+
tree.add(p.getVersionInUse());
112110
}
113111
final String unit = getCommonUnit(paths);
114112
if (unit != null)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public String toString() {
8787
}
8888

8989
public ProfileProcessor(final RandomAccessibleInterval<T> rai, final Path path) {
90-
this.path = (path.getUseFitted()) ? path.getFitted() : path;
90+
this.path = path.getVersionInUse();
9191
this.rai = rai;
9292
this.intervalMin = Intervals.minAsLongArray(rai);
9393
this.intervalMax = Intervals.maxAsLongArray(rai);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,8 @@ public TreeStatistics(final Collection<Path> paths, String label) {
381381
tree = new Tree(paths);
382382
tree.setLabel(label);
383383
for (final Path p : tree.list()) {
384-
// If fitted flavor of path exists use it instead
385-
if (p.getUseFitted()) {
384+
// tree.list() holds the versions in use: fitted flavors are fitted copies
385+
if (p.isFittedVersionOfAnotherPath()) {
386386
fittedPathsCounter++;
387387
}
388388
}

‎src/main/java/sc/fiji/snt/gui/cmds/ComputeSecondaryImg.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ private <V extends RealType<V>> Img<U> computeSpectralSimilarity(final U type, f
718718
// represent the actual structure the user is tracing
719719
final List<Path> resolvedPaths = new ArrayList<>(selectedPaths.size());
720720
for (final Path p : selectedPaths) {
721-
resolvedPaths.add(p.getUseFitted() && p.getFitted() != null ? p.getFitted() : p);
721+
resolvedPaths.add(p.getVersionInUse());
722722
}
723723
// See computeAndDisplayAverageColor() for why getActiveCanvasPixelOffset() is needed here
724724
final sc.fiji.snt.util.PointInCanvas offset = snt.getActiveCanvasPixelOffset();

0 commit comments

Comments
 (0)