@@ -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