Skip to content

Commit 2c3aa25

Browse files
committed
Fix: Zoom controls for paths/nodes not working in Stream mode
1 parent 793c3a7 commit 2c3aa25

3 files changed

Lines changed: 76 additions & 19 deletions

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,9 @@ private JPopupMenu getFilterVisibilityMenu() {
10631063
private JPopupMenu getToolsMenu() {
10641064
final JPopupMenu popup = new JPopupMenu();
10651065
GuiUtils.addSeparator(popup, "Navigation:");
1066-
popup.add(visitingZoom.zoomControls("Visiting Zoom Level", "issues"));
1066+
final JMenu zMenu = visitingZoom.zoomControls("Visiting Zoom Level", "issues");
1067+
zMenu.setEnabled(sntui != null && !sntui.isStreamMode());
1068+
popup.add(zMenu);
10671069
GuiUtils.addSeparator(popup, "Path Tagging:");
10681070
final JMenuItem colorMenuItem = new JMenuItem("Color Affected Paths by Issue Severity");
10691071
colorMenuItem.setIcon(IconFactory.menuIcon(IconFactory.GLYPH.DANGER));

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import sc.fiji.snt.plugin.*;
4444
import sc.fiji.snt.tracing.auto.SomaUtils;
4545
import sc.fiji.snt.util.*;
46+
import sc.fiji.snt.viewer.AbstractBigViewer;
4647

4748
import javax.swing.Timer;
4849
import javax.swing.*;
@@ -5893,6 +5894,7 @@ private JButton zoomToNodeButton() {
58935894
});
58945895
menu.addSeparator();
58955896
final JMenu zMenu = visitingZoom.zoomControls("Zoom to Node", "nodes");
5897+
zMenu.setEnabled(plugin.getUI() != null && !plugin.getUI().isStreamMode());
58965898
zMenu.setText("Visiting Zoom Level");
58975899
zMenu.setToolTipText("The magnification to be used when zooming into a node");
58985900
menu.add(zMenu);
@@ -6075,7 +6077,7 @@ private boolean canExecuteZoomOperation(final Collection<Path> paths) {
60756077
guiUtils.error("No path(s) selected.");
60766078
return false;
60776079
}
6078-
if (plugin.getImagePlus() == null) {
6080+
if (plugin.getImagePlus() == null && (plugin.getUI() == null || plugin.getUI().getActiveBigViewer() == null)) {
60796081
guiUtils.error("Image is not available.");
60806082
return false;
60816083
}
@@ -6102,10 +6104,13 @@ private void zoomToNode(final Path.PathNode node, GuiUtils.VisitingZoom visiting
61026104

61036105
private void zoomToBoundingBox(final Collection<Path> paths) {
61046106
if (!canExecuteZoomOperation(paths)) return;
6105-
final double prevMag = plugin.getImagePlus().getCanvas().getMagnification();
6106-
final double zoom = ImpUtils.zoomTo(plugin.getImagePlus(), paths);
6107-
plugin.setCanvasLabelAllPanes((zoom == prevMag) ? "Selected paths already in view" :
6108-
String.format("Zoomed to selected paths: (%.0f%%)", zoom * 100));
6107+
double zoom = GuiUtils.VisitingZoom.DEFAULT_PERCENT / 100d;
6108+
if (plugin.getImagePlus() != null) {
6109+
final double prevMag = plugin.getImagePlus().getCanvas().getMagnification();
6110+
zoom = ImpUtils.zoomTo(plugin.getImagePlus(), paths);
6111+
plugin.setCanvasLabelAllPanes((zoom == prevMag) ? "Selected paths already in view" :
6112+
String.format("Zoomed to selected paths: (%.0f%%)", zoom * 100));
6113+
}
61096114
zoomOtherPanesAsNeeded(paths, zoom);
61106115
final Timer timer = new Timer(600, ae -> plugin.setCanvasLabelAllPanes(null));
61116116
timer.setRepeats(false);
@@ -6122,6 +6127,20 @@ private void zoomOtherPanesAsNeeded(final Collection<Path> paths, final double m
61226127
if (xzImp != null)
61236128
ImpUtils.zoomTo(xzImp, magnification, paths, RoiConverter.XZ_PLANE);
61246129
}
6130+
flyActiveBigViewerToBoundingBoxAsNeeded(paths);
6131+
}
6132+
6133+
/**
6134+
* Flies the active Bdv/Bvv viewer (if any) to the bounding box of {@code paths}, via
6135+
* {@link AbstractBigViewer#flyTo(sc.fiji.snt.util.BoundingBox)}
6136+
*
6137+
* @param paths the paths whose bounding box the viewer should frame
6138+
*/
6139+
private void flyActiveBigViewerToBoundingBoxAsNeeded(final Collection<Path> paths) {
6140+
final AbstractBigViewer viewer = plugin.getUI().getActiveBigViewer();
6141+
if (viewer != null) {
6142+
viewer.flyTo(new Tree(new ArrayList<>(paths)).getBoundingBox());
6143+
}
61256144
}
61266145

61276146
void restoreFullModelState() {

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

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import sc.fiji.snt.gui.GuiUtils;
3232
import sc.fiji.snt.gui.IconFactory;
3333
import sc.fiji.snt.tracing.SearchInterface;
34+
import sc.fiji.snt.util.BoundingBox;
3435
import sc.fiji.snt.util.PointInImage;
3536
import sc.fiji.snt.util.SNTColor;
3637
import sc.fiji.snt.util.SNTPoint;
@@ -375,6 +376,50 @@ protected static void addToCardPanelBottom(final bdv.ui.CardPanel cardPanel, fin
375376
*/
376377
public abstract void setCurrentTimepoint(int timepoint);
377378

379+
/**
380+
* Animates the camera to frame the given world-coordinate bounding box: an isotropic scale is
381+
* computed so the box's width/height fit the viewport, and the transform is translated to
382+
* center the box's centroid on screen. Any existing rotation is dropped.
383+
* <p>
384+
* A degenerate box (zero width/height - e.g. a single-node selection, whose origin and
385+
* originOpposite are the same point) cannot be "fit" (there is nothing to scale to), so this
386+
* recenters on the centroid at the viewer's <em>current</em> zoom level instead.
387+
*
388+
* @param box the world-coordinate bounding box to frame; a no-op if {@code null}, its corners
389+
* are not real (e.g. an empty/uncomputed box), or the viewport has not yet been realized
390+
* @return {@code true} if the transform was computed and applied; {@code false} otherwise
391+
*/
392+
public boolean flyTo(final BoundingBox box) {
393+
if (box == null || !box.origin().isReal() || !box.originOpposite().isReal()) return false;
394+
final int cw = getViewerWidth(), ch = getViewerHeight();
395+
if (cw <= 0 || ch <= 0) return false;
396+
final double physW = box.width(), physH = box.height();
397+
final double scale;
398+
if (physW > 0 && physH > 0) {
399+
scale = Math.min(cw / physW, ch / physH);
400+
} else {
401+
// Degenerate (point-like) box: can't compute a fit scale, so keep the current zoom
402+
// and just recenter. Column norm rather than a raw matrix entry so this is correct
403+
// even if the current transform happens to carry rotation.
404+
final AffineTransform3D current = getViewerTransform();
405+
final double currentScale = Math.sqrt(
406+
current.get(0, 0) * current.get(0, 0) +
407+
current.get(1, 0) * current.get(1, 0) +
408+
current.get(2, 0) * current.get(2, 0));
409+
scale = (currentScale > 0 && Double.isFinite(currentScale)) ? currentScale : 1;
410+
}
411+
final SNTPoint centroid = box.getCentroid();
412+
final AffineTransform3D target = new AffineTransform3D();
413+
target.set(scale, 0, 0);
414+
target.set(scale, 1, 1);
415+
target.set(scale, 2, 2);
416+
target.set(cw / 2.0 - scale * centroid.getX(), 0, 3);
417+
target.set(ch / 2.0 - scale * centroid.getY(), 1, 3);
418+
target.set(-scale * centroid.getZ(), 2, 3);
419+
setViewerTransform(target, 300);
420+
return true;
421+
}
422+
378423
/**
379424
* Creates and returns a new {@link sc.fiji.snt.BookmarkManager} for this viewer.
380425
* Called exactly once (lazily) by {@link #getMarkerManager()}.
@@ -668,8 +713,6 @@ public void actionPerformed(final ActionEvent e) {
668713
showViewerMessage("No source selected");
669714
return;
670715
}
671-
final int cw = getViewerWidth(), ch = getViewerHeight();
672-
if (cw <= 0 || ch <= 0) return;
673716
final AffineTransform3D srcToWorld = new AffineTransform3D();
674717
src.getSpimSource().getSourceTransform(0, 0, srcToWorld);
675718
final net.imglib2.RandomAccessibleInterval<?> rai = src.getSpimSource().getSource(0, 0);
@@ -695,17 +738,10 @@ public void actionPerformed(final ActionEvent e) {
695738
minZ = Math.min(minZ, world[2]);
696739
maxZ = Math.max(maxZ, world[2]);
697740
}
698-
final double physW = maxX - minX, physH = maxY - minY, physZ = maxZ - minZ;
699-
if (physW <= 0 || physH <= 0) return;
700-
final double scale = Math.min(cw / physW, ch / physH);
701-
final AffineTransform3D target = new AffineTransform3D();
702-
target.set(scale, 0, 0);
703-
target.set(scale, 1, 1);
704-
target.set(scale, 2, 2);
705-
target.set(cw / 2.0 - scale * (minX + physW / 2.0), 0, 3);
706-
target.set(ch / 2.0 - scale * (minY + physH / 2.0), 1, 3);
707-
target.set(-scale * (minZ + physZ / 2.0), 2, 3);
708-
setViewerTransform(target, 300);
741+
final BoundingBox box = new BoundingBox();
742+
box.setOrigin(new PointInImage(minX, minY, minZ));
743+
box.setOriginOpposite(new PointInImage(maxX, maxY, maxZ));
744+
flyTo(box); // silently no-ops if the viewport isn't realized yet or the box is degenerate
709745
}
710746
};
711747
}

0 commit comments

Comments
 (0)