Skip to content

Commit fa87a5a

Browse files
committed
Label detector: Keep track of contacted label
1 parent fb52475 commit fa87a5a

5 files changed

Lines changed: 68 additions & 27 deletions

File tree

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ private void expandCapacityTo(final int n) {
282282
private void addToCapacity(final int n) {
283283
final int currentN = delineations.size();
284284
final Color[] colors = defaultDelineationColors(Math.min(256, currentN + n));
285-
final long lastLabel = delineations.isEmpty() ? 0 : delineations.getLast().label;
285+
final long lastLabel = delineations.stream().mapToLong(d -> d.label).max().orElse(0);
286286
int colorIdx = 0;
287287
for (int i = 0; i < n; i++) {
288288
final Delineation del = new Delineation(lastLabel + i + 1, colors[colorIdx++]);
@@ -1243,6 +1243,9 @@ private class Delineation {
12431243
// labels are applied as negative numbers, so we'll impose positive labels
12441244
throw new IllegalArgumentException("Label must be greater than 0 and not " + UNDEFINED_LABEL);
12451245
}
1246+
if (delineations.stream().anyMatch(d -> d.label == label)) {
1247+
throw new IllegalArgumentException("Duplicate delineation label: " + label);
1248+
}
12461249
this.color = color;
12471250
this.label = label;
12481251
name = "Delineation " + label;
@@ -1336,7 +1339,10 @@ private JButton assignFromRoiButton() {
13361339
"No Paths in ROI")) {
13371340
return;
13381341
}
1339-
if (this.roi != null) untagNodes(paths);
1342+
if (this.roi != null) {
1343+
// Clear stale labels from the OLD roi's paths, not the new one
1344+
untagNodes((this.roi == DUMMY_ROI) ? pafm.getPaths() : pafm.getPathsInROI(this.roi));
1345+
}
13401346
assignRoi(roi, paths);
13411347
sntui.plugin.updateAllViewers();
13421348
}
@@ -1417,16 +1423,12 @@ private void updateWidget() {
14171423
public boolean equals(Object o) {
14181424
if (this == o) return true;
14191425
if (o == null || getClass() != o.getClass()) return false;
1420-
Delineation that = (Delineation) o;
1421-
return label == that.label &&
1422-
Objects.equals(name, that.name) &&
1423-
Objects.equals(color, that.color) &&
1424-
Objects.equals(roi, that.roi);
1426+
return label == ((Delineation) o).label;
14251427
}
14261428

14271429
@Override
14281430
public int hashCode() {
1429-
return Objects.hash(label, name, color, roi);
1431+
return Long.hashCode(label);
14301432
}
14311433
}
14321434

‎src/main/java/sc/fiji/snt/analysis/detection/Detection.java‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ public class Detection {
5050
public final int nodeIndex;
5151
/** Distance from the path skeleton at the detection point (physical units) */
5252
public final double distanceFromSkeleton;
53+
/**
54+
* Label value from a segmentation/label image associated with this
55+
* detection, or {@code -1} if not applicable. Set by
56+
* {@link LabelProximityDetector} to identify which label surface
57+
* produced the contact.
58+
*/
59+
public final int labelValue;
5360

5461
/**
5562
* Creates a new detection.
@@ -65,13 +72,33 @@ public class Detection {
6572
public Detection(final double x, final double y, final double z,
6673
final double intensity, final Path path, final int nodeIndex,
6774
final double distanceFromSkeleton) {
75+
this(x, y, z, intensity, path, nodeIndex, distanceFromSkeleton, -1);
76+
}
77+
78+
/**
79+
* Creates a new detection with an associated label value.
80+
*
81+
* @param x X-coordinate in real-world units
82+
* @param y Y-coordinate in real-world units
83+
* @param z Z-coordinate in real-world units
84+
* @param intensity intensity at the detection site
85+
* @param path associated path
86+
* @param nodeIndex index of the nearest node on the path
87+
* @param distanceFromSkeleton distance from the skeleton (physical units)
88+
* @param labelValue label-image value associated with this
89+
* detection, or {@code -1} if not applicable
90+
*/
91+
public Detection(final double x, final double y, final double z,
92+
final double intensity, final Path path, final int nodeIndex,
93+
final double distanceFromSkeleton, final int labelValue) {
6894
this.x = x;
6995
this.y = y;
7096
this.z = z;
7197
this.intensity = intensity;
7298
this.path = path;
7399
this.nodeIndex = nodeIndex;
74100
this.distanceFromSkeleton = distanceFromSkeleton;
101+
this.labelValue = labelValue;
75102
}
76103

77104
/**
@@ -105,9 +132,12 @@ public double[] xyzct() {
105132

106133
@Override
107134
public String toString() {
108-
return String.format(
109-
"Detection[x=%.3f,y=%.3f,z=%.3f; I=%.1f; dist=%.3f; path=%s; node=%d]",
135+
final String base = String.format(
136+
"Detection[x=%.3f,y=%.3f,z=%.3f; I=%.1f; dist=%.3f; path=%s; node=%d",
110137
x, y, z, intensity, distanceFromSkeleton,
111138
path != null ? path.getName() : "null", nodeIndex);
139+
return (labelValue >= 0)
140+
? base + "; label=" + labelValue + "]"
141+
: base + "]";
112142
}
113143
}

‎src/main/java/sc/fiji/snt/analysis/detection/DetectorUtils.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public static List<Detection> assignToNearestPaths(final List<Detection> detecti
181181

182182
if (nearest != v.path || nearestIdx != v.nodeIndex) {
183183
reassigned.add(new Detection(v.x, v.y, v.z, v.intensity,
184-
nearest, nearestIdx, Math.sqrt(minDist2)));
184+
nearest, nearestIdx, Math.sqrt(minDist2), v.labelValue));
185185
} else {
186186
reassigned.add(v);
187187
}

‎src/main/java/sc/fiji/snt/analysis/detection/LabelProximityDetector.java‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static List<Detection> detect(final Collection<Path> paths,
102102
final int nodeIdx = (int) entry[0];
103103
final double dist = entry[1];
104104
if (dist <= config.distanceThreshold) {
105-
allDetections.add(createDetection(path, nodeIdx, dist));
105+
allDetections.add(createDetection(path, nodeIdx, dist, labelVal));
106106
}
107107
}
108108
} else {
@@ -116,7 +116,7 @@ public static List<Detection> detect(final Collection<Path> paths,
116116
}
117117
}
118118
if (minIdx >= 0) {
119-
allDetections.add(createDetection(path, minIdx, minDist));
119+
allDetections.add(createDetection(path, minIdx, minDist, labelVal));
120120
}
121121
}
122122
}
@@ -138,15 +138,22 @@ public static List<Detection> detect(final Collection<Path> paths,
138138
}
139139

140140
private static Detection createDetection(final Path path, final int nodeIdx,
141-
final double distance) {
141+
final double distance,
142+
final int labelVal) {
142143
final PointInImage node = path.getNode(nodeIdx);
143144
// NMS keeps the highest score, so negate distance so that the closest
144145
// contact point (smallest distance) survives deduplication
145146
return new Detection(node.x, node.y, node.z,
146147
-distance,
147-
path, nodeIdx, 0); // distanceFromSkeleton = 0 (detection is on-skeleton)
148+
path, nodeIdx, 0, // distanceFromSkeleton = 0 (detection is on-skeleton)
149+
labelVal);
148150
}
149151

152+
/**
153+
* Collects unique non-zero, positive integer label values from the image.
154+
* Negative values and non-integer values are excluded, consistent with
155+
* the convention that 0 = background and positive integers = labels.
156+
*/
150157
private static Set<Integer> collectLabels(final RandomAccessibleInterval<? extends RealType<?>> img) {
151158
final Set<Integer> labels = new LinkedHashSet<>();
152159
final Cursor<? extends RealType<?>> cursor = img.cursor();

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,22 @@ public void run() {
179179

180180
SNTUtils.log("Detected " + results.size() + " proximity contacts");
181181

182-
// Group results by path, resolving fitted paths back to unfitted originals
183-
final Map<Path, List<Detection>> resultsByPath = results.stream()
184-
.collect(Collectors.groupingBy(d ->
185-
d.path.isFittedVersionOfAnotherPath()
186-
? d.path.getUnfitted() : d.path
187-
));
182+
// Group results by path+label, resolving fitted paths back to unfitted originals
183+
final Map<String, List<Detection>> resultsByPathAndLabel = results.stream()
184+
.collect(Collectors.groupingBy(d -> {
185+
final Path p = d.path.isFittedVersionOfAnotherPath()
186+
? d.path.getUnfitted() : d.path;
187+
return p.getName() + (d.labelValue >= 0 ? " [Label " + d.labelValue + "]" : "");
188+
}, LinkedHashMap::new, Collectors.toList()));
188189

189190
if (ui != null && outputChoice.toLowerCase().contains("bookmark")) {
190191

191-
resultsByPath.forEach((path, detections) -> {
192+
resultsByPathAndLabel.forEach((groupName, detections) -> {
192193
final List<double[]> locs = detections.stream()
193194
.map(Detection::xyzct)
194195
.collect(Collectors.toList());
195-
ui.getBookmarkManager().add(path.getName() + " Proximity ", locs, path.getColor());
196+
final Path refPath = detections.getFirst().path;
197+
ui.getBookmarkManager().add(groupName, locs, refPath.getColor());
196198
});
197199
resetUI();
198200
ui.selectTab("Bookmarks");
@@ -206,10 +208,10 @@ public void run() {
206208
}
207209
RoiManager rm = RoiManager.getInstance2();
208210
if (rm == null) rm = new RoiManager();
209-
for (final Map.Entry<Path, List<Detection>> entry : resultsByPath.entrySet()) {
210-
final Path path = entry.getKey();
211-
final String name = path.getName() + " (" + entry.getValue().size() + " contacts)";
212-
rm.addRoi(RoiConverter.toPointRoi(entry.getValue(), imp, name, path.getColor()));
211+
for (final Map.Entry<String, List<Detection>> entry : resultsByPathAndLabel.entrySet()) {
212+
final String name = entry.getKey() + " (" + entry.getValue().size() + " contacts)";
213+
final Path refPath = entry.getValue().getFirst().path;
214+
rm.addRoi(RoiConverter.toPointRoi(entry.getValue(), imp, name, refPath.getColor()));
213215
}
214216
resetUI();
215217
rm.runCommand("sort");

0 commit comments

Comments
 (0)