102102import java .util .*;
103103import java .util .concurrent .*;
104104import java .util .function .BiFunction ;
105+ import java .util .function .Consumer ;
105106
106107
107108/**
@@ -962,21 +963,25 @@ private void loadDatasetFromImagePlus(final ImagePlus imp) {
962963 }
963964
964965 public void startUI () {
966+ startUI (false );
967+ }
968+
969+ public void startUI (final boolean bigDataMode ) {
965970 if (SwingUtilities .isEventDispatchThread ()) {
966- startUIOnEDT ();
971+ startUIOnEDT (bigDataMode );
967972 } else {
968973 try {
969- SwingUtilities .invokeAndWait (this :: startUIOnEDT );
974+ SwingUtilities .invokeAndWait (() -> startUIOnEDT ( bigDataMode ) );
970975 } catch (final Throwable e ) {
971976 throw new RuntimeException ("Failed to start UI" , e );
972977 }
973978 }
974979 }
975980
976- private void startUIOnEDT () {
981+ private void startUIOnEDT (final boolean bigDataMode ) {
977982 GuiUtils .setLookAndFeel ();
978983 final SNT thisPlugin = this ;
979- ui = new SNTUI (thisPlugin );
984+ ui = new SNTUI (thisPlugin , bigDataMode );
980985 guiUtils = new GuiUtils (ui );
981986 ui .displayOnStarting ();
982987 }
@@ -2380,6 +2385,32 @@ public Path manualTrace(final SNTPoint start, final SNTPoint end, final PointInI
23802385 return manualTraceHeadless (List .of (start , end ), forkPoint );
23812386 }
23822387
2388+ /**
2389+ * Headless auto-trace variant that additionally reports progress and exposes the underlying
2390+ * {@link Future} for cancellation. Always runs headless (regardless of {@link
2391+ * GraphicsEnvironment#isHeadless()}); intended for callers (e.g., BVV's tracing toggle) that
2392+ * need live feedback and/or the ability to cancel a slow search, e.g., over a lazily-loaded,
2393+ * network-backed image where a single segment can take a long time to converge (or never
2394+ * converge, if the goal is unreachable).
2395+ *
2396+ * @param start the start point
2397+ * @param end the end point
2398+ * @param forkPoint the fork point of the parent {@link Path} from which the path should
2399+ * branch off, or null if the path should not have any parent
2400+ * @param progressCallback receives periodic {@code pointsInSearch}/{@code finished}/{@code
2401+ * threadStatus} updates (roughly once a second; see {@code reportEveryMilliseconds}),
2402+ * or null to skip progress reporting
2403+ * @param onSubmit called with the search's {@link Future} immediately after it is submitted to
2404+ * the tracer thread pool, so the caller can cancel it later (e.g. {@code
2405+ * future.cancel(true)}); the search loop already checks for interruption, so this
2406+ * actually stops the search rather than merely abandoning it. May be null.
2407+ * @return the path, or null if the search failed or was cancelled
2408+ */
2409+ public Path autoTrace (final SNTPoint start , final SNTPoint end , final PointInImage forkPoint ,
2410+ final SearchProgressCallback progressCallback , final Consumer <Future <?>> onSubmit ) {
2411+ return autoTraceHeadless (List .of (start , end ), forkPoint , progressCallback , onSubmit );
2412+ }
2413+
23832414 /**
23842415 * Automatically traces a path from a list of points and adds it to the active
23852416 * {@link PathAndFillManager} instance. Note that this method still requires
@@ -2480,9 +2511,14 @@ public Path autoTrace(final List<SNTPoint> pointList, final PointInImage forkPoi
24802511 }
24812512
24822513 private Path autoTraceHeadless (final List <SNTPoint > pointList , final PointInImage forkPoint ) {
2514+ return autoTraceHeadless (pointList , forkPoint , null , null );
2515+ }
2516+
2517+ private Path autoTraceHeadless (final List <SNTPoint > pointList , final PointInImage forkPoint ,
2518+ final SearchProgressCallback progressCallback , final Consumer <Future <?>> onSubmit ) {
24832519 return runHeadlessTrace (pointList , forkPoint , (start , end ) -> createSearch (
24842520 start .getX (), start .getY (), start .getZ (),
2485- end .getX (), end .getY (), end .getZ ()));
2521+ end .getX (), end .getY (), end .getZ ()), progressCallback , onSubmit );
24862522 }
24872523
24882524 /**
@@ -2494,7 +2530,7 @@ private Path autoTraceHeadless(final List<SNTPoint> pointList, final PointInImag
24942530 private Path manualTraceHeadless (final List <SNTPoint > pointList , final PointInImage forkPoint ) {
24952531 return runHeadlessTrace (pointList , forkPoint , (start , end ) -> new ManualTracerThread (this ,
24962532 start .getX () / x_spacing , start .getY () / y_spacing , start .getZ () / z_spacing ,
2497- end .getX () / x_spacing , end .getY () / y_spacing , end .getZ () / z_spacing ));
2533+ end .getX () / x_spacing , end .getY () / y_spacing , end .getZ () / z_spacing ), null , null );
24982534 }
24992535
25002536 /**
@@ -2514,10 +2550,15 @@ private Path manualTraceHeadless(final List<SNTPoint> pointList, final PointInIm
25142550 * @param pointList the nodes to connect, start to end
25152551 * @param forkPoint optional fork point of the parent Path, or null
25162552 * @param searchFactory builds the per-segment search for a given start/end pair
2553+ * @param progressCallback registered on each per-segment search via {@link
2554+ * SearchInterface#addProgressListener}, or null to skip progress reporting
2555+ * @param onSubmit called with each per-segment search's {@link Future} immediately after
2556+ * submission (so a caller can cancel it later), or null
25172557 * @return the stitched path, or null if any segment failed to produce a result
25182558 */
25192559 private <S extends Runnable & SearchInterface > Path runHeadlessTrace (final List <SNTPoint > pointList ,
2520- final PointInImage forkPoint , final BiFunction <SNTPoint , SNTPoint , S > searchFactory )
2560+ final PointInImage forkPoint , final BiFunction <SNTPoint , SNTPoint , S > searchFactory ,
2561+ final SearchProgressCallback progressCallback , final Consumer <Future <?>> onSubmit )
25212562 {
25222563 if (pointList == null || pointList .isEmpty ())
25232564 throw new IllegalArgumentException ("pointList cannot be null or empty" );
@@ -2536,10 +2577,13 @@ private <S extends Runnable & SearchInterface> Path runHeadlessTrace(final List<
25362577 Path pathResult = null ;
25372578 try {
25382579 final S search = searchFactory .apply (start , end );
2580+ if (progressCallback != null ) search .addProgressListener (progressCallback );
25392581 final Future <?> result = tracerThreadPool .submit (search );
2582+ if (onSubmit != null ) onSubmit .accept (result );
25402583 result .get ();
25412584 pathResult = search .getResult ();
2542- } catch (InterruptedException | ExecutionException | IllegalArgumentException e ) {
2585+ } catch (InterruptedException | ExecutionException | IllegalArgumentException
2586+ | CancellationException e ) {
25432587 SNTUtils .error ("Error during trace" , e );
25442588 }
25452589 if (pathResult == null ) {
@@ -3492,6 +3536,9 @@ public boolean isOnlySelectedPathsVisible() {
34923536
34933537 protected void updateTracingViewers (final boolean includeLegacy3Dviewer ) {
34943538 repaintAllPanes ();
3539+ if (getUI () != null && getUI ().bvvSNT != null ) {
3540+ new Thread (() -> getUI ().bvvSNT .syncPathManagerList ()).start ();
3541+ }
34953542 if (includeLegacy3Dviewer ) update3DViewerContents ();
34963543 }
34973544
@@ -3503,6 +3550,9 @@ protected void updateNonTracingViewers() {
35033550 if (getUI ().sciViewSNT != null ) {
35043551 new Thread (() -> getUI ().sciViewSNT .syncPathManagerList ()).start ();
35053552 }
3553+ if (getUI ().bdvSNT != null ) {
3554+ new Thread (() -> getUI ().bdvSNT .syncPathManagerList ()).start ();
3555+ }
35063556 }
35073557
35083558 public void updateAllViewers () {
@@ -4204,6 +4254,58 @@ public int getDepth() {
42044254 return depth ;
42054255 }
42064256
4257+ /**
4258+ * Sets image dimensions and calibration without providing actual pixel data. Useful when the
4259+ * source image is not directly accessible as random-access data (e.g., a BigDataViewer
4260+ * {@code AbstractSpimData}/IMS source, or an ambiguous N5/Zarr layout) but its metadata (size,
4261+ * voxel spacing) is still known. This allows features that only need bounds/scale (e.g., manual
4262+ * tracing, bounds/distance checks) to work correctly with the image's real dimensions, even
4263+ * though {@link #accessToValidImageData()} keeps reporting no data is available (this does NOT
4264+ * set {@code ctSlice3d}, so A* search remains unavailable).
4265+ *
4266+ * @param width width in pixels (ignored if ≤0)
4267+ * @param height height in pixels (ignored if ≤0)
4268+ * @param depth depth (number of slices) in pixels (ignored if ≤0)
4269+ * @param xSpacing pixel width (ignored if ≤0)
4270+ * @param ySpacing pixel height (ignored if ≤0)
4271+ * @param zSpacing pixel depth (ignored if ≤0)
4272+ * @param units spatial calibration units (ignored if null/blank)
4273+ */
4274+ public void setImageMetadata (final int width , final int height , final int depth , final double xSpacing ,
4275+ final double ySpacing , final double zSpacing , final String units ) {
4276+ if (width > 0 ) this .width = width ;
4277+ if (height > 0 ) this .height = height ;
4278+ if (depth > 0 ) {
4279+ this .depth = depth ;
4280+ this .singleSlice = depth == 1 ;
4281+ }
4282+ if (xSpacing > 0 ) this .x_spacing = xSpacing ;
4283+ if (ySpacing > 0 ) this .y_spacing = ySpacing ;
4284+ if (zSpacing > 0 ) this .z_spacing = zSpacing ;
4285+ if (units != null && !units .isBlank ()) this .spacing_units = SNTUtils .getSanitizedUnit (units );
4286+ }
4287+
4288+ /**
4289+ * Directly sets the image data backing A* search, without requiring an {@link ImgPlus} wrapper.
4290+ * Useful when pixel data comes from a source whose {@link RandomAccessibleInterval} is already
4291+ * resolved elsewhere (e.g., a BigDataViewer/SpimData {@code ImgLoader}, or a BDV/BVV
4292+ * {@code Source}), and building/discarding a full {@code ImgPlus} just to satisfy
4293+ * {@link #SNT(ImgPlus)} would be wasteful, or would not preserve lazy/chunked access the way the
4294+ * original object does.
4295+ * <p>
4296+ * Callers remain responsible for also calling {@link #setImageMetadata} (or equivalent) with matching
4297+ * dimensions/calibration; this method does not attempt to infer them from {@code data}.
4298+ * <p>
4299+ * {@code data}'s pixel type must be a {@link RealType}; other types (e.g. {@code ARGBType}) will
4300+ * throw a {@code ClassCastException} later, e.g., the first time A* search accesses a pixel.
4301+ *
4302+ * @param data the (possibly lazily-backed) image data, in the same pixel/voxel grid implied by
4303+ * the dimensions/calibration set via {@link #setImageMetadata}
4304+ */
4305+ public void setImageData (final RandomAccessibleInterval <?> data ) {
4306+ this .ctSlice3d = data ;
4307+ }
4308+
42074309 public double getPixelWidth () {
42084310 return x_spacing ;
42094311 }
0 commit comments