Skip to content

Commit 453c490

Browse files
committed
Fix: erratic 3D positions from mouse clicks in Bvv
1 parent ce2e9ba commit 453c490

2 files changed

Lines changed: 179 additions & 24 deletions

File tree

‎src/main/java/sc/fiji/snt/viewer/Bvv.java‎

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ public class Bvv extends AbstractBigViewer {
7878

7979
static { net.imagej.patcher.LegacyInjector.preinit(); } // required for _every_ class that imports ij. classes
8080

81+
/**
82+
* Minimum normalized (0-1, see {@link BvvUtils#peakValue}) intensity for a ray-max result to be treated as a
83+
* genuine hit on visible signal rather than background/noise. Below this, {@link #findClickRayMaxima()} widens/
84+
* escalates the search radius instead of accepting the point: a window can contain *some* nonzero sample without
85+
* containing the structure actually being clicked
86+
*/
87+
private static final double PEAK_SIGNIFICANCE = 0.2;
88+
8189
/**
8290
* Returns the most recently created {@link Bvv} instance, or {@code null}
8391
* if none has been created yet. Convenience accessor for scripts.
@@ -1665,6 +1673,12 @@ private void handleClick(final java.awt.event.MouseEvent e) {
16651673
final double[] viewerPt = new double[3];
16661674
current.apply(target, viewerPt);
16671675

1676+
if (SNTUtils.isDebugMode()) {
1677+
SNTUtils.log(String.format(
1678+
"BVV recenter click: screen=(%d,%d) target=(%.3f,%.3f,%.3f) viewerZ=%.3f",
1679+
e.getX(), e.getY(), target[0], target[1], target[2], viewerPt[2]));
1680+
}
1681+
16681682
if (reactTracingEnabled && renderingOptions.strategy == RecenterStrategy.ADAPTIVE) {
16691683
// Skip the recenter entirely when the click already lands close enough to the
16701684
// focal plane that foreshortening error is negligible. This is the common case
@@ -2405,35 +2419,81 @@ public double[] findClickRayMaxima() {
24052419
(current != null && visible.contains(current))
24062420
? java.util.Collections.singleton(current)
24072421
: visible;
2408-
// Tight radius first (keeps the result tethered to the clicked point, see
2409-
// BvvUtils#RAY_SEARCH_RADIUS_VOXELS); only widen if that finds nothing at all, so a click
2410-
// that's merely a few voxels off a thin/dim structure doesn't silently come back empty
2411-
double[] peak = searchPeakAcrossSources(toSample, tp, focalT, ray, vp, near, far, BvvUtils.RAY_SEARCH_RADIUS_VOXELS);
2412-
if (peak == null) {
2413-
peak = searchPeakAcrossSources(toSample, tp, focalT, ray, vp, near, far, BvvUtils.RAY_SEARCH_RADIUS_VOXELS_WIDE);
2414-
if (peak != null)
2415-
SNTUtils.log("BVV: ray-max found nothing within " + BvvUtils.RAY_SEARCH_RADIUS_VOXELS
2416-
+ " voxels of the clicked point; widened to " + BvvUtils.RAY_SEARCH_RADIUS_VOXELS_WIDE);
2422+
if (SNTUtils.isDebugMode()) {
2423+
SNTUtils.log("BVV findClickRayMaxima: focalT=" + focalT + " near=" + near + " far=" + far
2424+
+ " sampling " + toSample.size() + " source(s)"
2425+
+ (current != null ? " (current-only: " + current.getSpimSource().getName() + ")" : ""));
2426+
}
2427+
2428+
// Tight radius first (keeps the result tethered to the clicked point, see BvvUtils#RAY_SEARCH_RADIUS_VOXELS);
2429+
// only widen if that finds nothing significant, so a click that's merely a few voxels off a thin/dim structure
2430+
// doesn't silently come back empty. "Significant" (see PEAK_SIGNIFICANCE) matters as much as "found": a window
2431+
// can contain some nonzero sample (background/noise, a few % of the display range) without containing the
2432+
// actually-visible structure the user clicked on, which may sit outside the window entirely
2433+
double[] result = searchPeakAcrossSources(toSample, tp, focalT, ray, vp, near, far, BvvUtils.RAY_SEARCH_RADIUS_VOXELS);
2434+
if (result == null || result[3] < PEAK_SIGNIFICANCE) {
2435+
final double[] wide = searchPeakAcrossSources(toSample, tp, focalT, ray, vp, near, far, BvvUtils.RAY_SEARCH_RADIUS_VOXELS_WIDE);
2436+
if (wide != null && (result == null || wide[3] > result[3])) {
2437+
if (result == null) SNTUtils.log("BVV: ray-max found nothing within "
2438+
+ BvvUtils.RAY_SEARCH_RADIUS_VOXELS + " voxels of the clicked point; widened to "
2439+
+ BvvUtils.RAY_SEARCH_RADIUS_VOXELS_WIDE);
2440+
result = wide;
2441+
}
2442+
}
2443+
if (result == null || result[3] < PEAK_SIGNIFICANCE) {
2444+
// Neither window found a peak clearly above background: the visible structure is not near the view's
2445+
// current focal plane at all (common right after loading a dataset, or after navigating without scrolling
2446+
// depth to match). Fall back to searching the ray's entire near-to-far extent rather than settling for a
2447+
// dim/background sample or perpetually returning the (likely wrong) focal-plane position
2448+
final double[] full = searchPeakAcrossSources(toSample, tp, focalT, ray, vp, near, far, BvvUtils.RAY_SEARCH_RADIUS_VOXELS_FULL);
2449+
if (full != null && (result == null || full[3] > result[3])) {
2450+
SNTUtils.log("BVV: ray-max found nothing significant within " + BvvUtils.RAY_SEARCH_RADIUS_VOXELS_WIDE
2451+
+ " voxels of the focal plane either; searched the full ray instead");
2452+
result = full;
2453+
}
2454+
}
2455+
final double[] peak = (result == null) ? null : new double[]{result[0], result[1], result[2]};
2456+
if (SNTUtils.isDebugMode()) {
2457+
SNTUtils.log("BVV findClickRayMaxima: final peak = "
2458+
+ (result == null ? "null" : String.format("(%.3f,%.3f,%.3f) normVal=%.4f",
2459+
result[0], result[1], result[2], result[3])));
24172460
}
24182461
return peak;
24192462
}
24202463

2421-
/** Searches all of {@code toSample} for the highest-value ray-max peak within {@code searchRadiusVoxels} */
2464+
/**
2465+
* Searches all of {@code toSample} for the highest-value ray-max peak within {@code searchRadiusVoxels}.
2466+
*
2467+
* @return {@code {x, y, z, normalizedValue}}, or null if no source produced any peak at all
2468+
*/
24222469
private double[] searchPeakAcrossSources(final java.util.Collection<bdv.viewer.SourceAndConverter<?>> toSample,
24232470
final int tp, final double focalT, final double[][] ray,
24242471
final VolumeViewerPanel vp, final double near, final double far,
24252472
final double searchRadiusVoxels) {
24262473
double[] bestPeak = null;
24272474
double bestVal = Double.NEGATIVE_INFINITY;
2475+
final bdv.viewer.ConverterSetups converterSetups = (bvvHandle != null) ? bvvHandle.getConverterSetups() : null;
24282476
for (final bdv.viewer.SourceAndConverter<?> sac : toSample) {
24292477
final bdv.viewer.Source<?> src = sac.getSpimSource();
24302478
final int level = BvvUtils.bestMipLevel(vp, src, tp, pathOverlay.overlayRenderer.dCam, near, far);
24312479
final double[] p = BvvUtils.rayMaxima(ray[0], ray[1], src, tp, focalT, level, searchRadiusVoxels);
24322480
if (p == null) continue;
2433-
final double val = BvvUtils.peakValue(p, src, tp, level);
2481+
// Normalize by each source's own display range so a channel with larger raw values
2482+
// (e.g. 16-bit vs 8-bit, or simply un-normalized contrast) doesn't always win the
2483+
// comparison regardless of which channel is actually visible at the clicked point
2484+
final ConverterSetup setup = (converterSetups != null) ? converterSetups.getConverterSetup(sac) : null;
2485+
final double dispMin = (setup != null) ? setup.getDisplayRangeMin() : 0;
2486+
final double dispMax = (setup != null) ? setup.getDisplayRangeMax() : 255;
2487+
final double val = BvvUtils.peakValue(p, src, tp, level, dispMin, dispMax);
2488+
if (SNTUtils.isDebugMode()) {
2489+
SNTUtils.log(String.format(
2490+
"BVV searchPeakAcrossSources[radius=%.0f]: '%s' level=%d dispRange=[%.1f,%.1f] "
2491+
+ "point=(%.3f,%.3f,%.3f) normVal=%.4f",
2492+
searchRadiusVoxels, src.getName(), level, dispMin, dispMax, p[0], p[1], p[2], val));
2493+
}
24342494
if (val > bestVal) { bestVal = val; bestPeak = p; }
24352495
}
2436-
return bestPeak;
2496+
return (bestPeak == null) ? null : new double[]{bestPeak[0], bestPeak[1], bestPeak[2], bestVal};
24372497
}
24382498

24392499
@Override

0 commit comments

Comments
 (0)