Skip to content

Commit 57a870d

Browse files
authored
Merge pull request #334 from morphonets/edit-rewrite
Extend edit controls to Bdv/Bvv
2 parents 3d07489 + 8e50ba4 commit 57a870d

17 files changed

Lines changed: 3277 additions & 1149 deletions

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

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -451,17 +451,14 @@ private void goToRow(final int tableRow) {
451451
// viewer mode: animate camera to marker world position
452452
flyTo(tableRow);
453453
} else {
454+
// Classic mode: move the image windows and any open Bvv/Bdv viewer
455+
final Bookmark b = model.getDataList().get(table.convertRowIndexToModel(tableRow));
456+
final int nViewers = flyOpenViewers(b);
454457
final ImagePlus imp = sntui.plugin.getImagePlus();
455458
if (imp == null) {
456-
noImageOpenError();
459+
if (nViewers == 0) noImageOpenError();
457460
} else {
458-
goTo(tableRow, imp);
459-
if (!sntui.plugin.getSinglePane()) {
460-
final ImagePlus zyImp = sntui.plugin.getImagePlus(SNT.ZY_PLANE);
461-
if (zyImp != null) goTo(tableRow, zyImp, SNT.ZY_PLANE);
462-
final ImagePlus xzImp = sntui.plugin.getImagePlus(SNT.XZ_PLANE);
463-
if (xzImp != null) goTo(tableRow, xzImp, SNT.XZ_PLANE);
464-
}
461+
goToAllPlanes(sntui.plugin, b, false);
465462
}
466463
}
467464
}
@@ -1303,7 +1300,7 @@ public void actionPerformed(final ActionEvent e) {
13031300
viewer.promptGoTo();
13041301
return;
13051302
}
1306-
if (sntui.plugin.getImagePlus() == null) {
1303+
if (sntui.plugin.getImagePlus() == null && sntui.getOpenBigViewers().isEmpty()) {
13071304
noImageOpenError();
13081305
return;
13091306
}
@@ -1317,13 +1314,8 @@ public void actionPerformed(final ActionEvent e) {
13171314
final PointInImage pim = SNTPoint.fromString(pos);
13181315
final Bookmark b = new Bookmark(String.format("%.2f, %.2f, %.2f", pim.x, pim.y, pim.z),
13191316
pim.x, pim.y, pim.z, 1, 1);
1320-
goTo(b, sntui.plugin.getImagePlus(), SNT.XY_PLANE);
1321-
if (!sntui.plugin.getSinglePane()) {
1322-
final ImagePlus zyImp = sntui.plugin.getImagePlus(SNT.ZY_PLANE);
1323-
if (zyImp != null) goTo(b, zyImp, SNT.ZY_PLANE);
1324-
final ImagePlus xzImp = sntui.plugin.getImagePlus(SNT.XZ_PLANE);
1325-
if (xzImp != null) goTo(b, xzImp, SNT.XZ_PLANE);
1326-
}
1317+
flyOpenViewers(b);
1318+
goToAllPlanes(sntui.plugin, b, false);
13271319
sntui.showStatus(String.format("Zoomed to %s", b.label), true);
13281320
} catch (final Throwable ex) {
13291321
getGuiUtils().error("Could not extract a valid location from \"" + pos + "\".");
@@ -1367,11 +1359,14 @@ private boolean noBookmarksError() {
13671359
return false;
13681360
}
13691361

1370-
private void goTo(final int row, final ImagePlus imp, final int plane) {
1371-
goTo(model.getDataList().get(table.convertRowIndexToModel(row)), imp, plane);
1372-
}
13731362

1374-
private void goTo(final Bookmark b, final ImagePlus imp, final int plane) {
1363+
/**
1364+
* Moves the display of an image to a bookmark
1365+
*
1366+
* @param silent if true, no error is displayed if the bookmark lies outside the image
1367+
* @return true if the bookmark is within the image (and the display was moved)
1368+
*/
1369+
private boolean goTo(final Bookmark b, final ImagePlus imp, final int plane, final boolean silent) {
13751370
assert imp != null;
13761371

13771372
// Bookmark coordinates are always world/calibrated; convert to pixel space, which is
@@ -1400,33 +1395,80 @@ private void goTo(final Bookmark b, final ImagePlus imp, final int plane) {
14001395
}
14011396
};
14021397

1403-
if (viewX > imp.getWidth() || viewY > imp.getHeight()) {
1398+
final boolean outsideXY = viewX < 0 || viewY < 0 || viewX > imp.getWidth() || viewY > imp.getHeight();
1399+
// Z is only checked on the main XY plane: side views show all Z by definition
1400+
final boolean outsideZ = plane == SNT.XY_PLANE && (pz < 0 || pz >= imp.getNSlices());
1401+
if (outsideXY || outsideZ) {
14041402
// Only show error for the main XY plane
1405-
if (plane == SNT.XY_PLANE) {
1406-
sntui.guiUtils.error("Location is outside image XY dimensions");
1403+
if (plane == SNT.XY_PLANE && !silent) {
1404+
final SNT snt = snt();
1405+
final String what = (snt != null && snt.isMaterializedCrop()) ? "the materialized crop" : "image";
1406+
getGuiUtils().error((outsideXY && outsideZ) ? "Location is outside " + what + " bounds"
1407+
: (outsideXY) ? "Location is outside " + what + " XY dimensions"
1408+
: "Location is outside " + what + " Z range");
14071409
}
1408-
return;
1410+
return false;
14091411
}
14101412
if (plane == SNT.XY_PLANE) {
14111413
// NB: ImagePlus#setPosition uses 1-based slice indices; pz is 0-based (see worldToPixel)
14121414
imp.setPosition(b.c, (int) pz + 1, b.t);
14131415
}
14141416
// Side views don't need setPosition - they show all Z by definition
14151417
ImpUtils.zoomTo(imp, visitingZoom.fraction(), (int) viewX, (int) viewY);
1418+
return true;
14161419
}
14171420

1418-
private void goTo(final int row, final ImagePlus imp) {
1419-
goTo(row, imp, SNT.XY_PLANE);
1421+
/**
1422+
* Viewer mode: moves the image windows of a materialized crop (Stream mode), if any, to a bookmark. Bookmarks
1423+
* outside the crop, which is only a portion of the streamed data, are skipped with a notice in the viewer
1424+
*/
1425+
private void goToMaterializedCrop(final Bookmark b) {
1426+
final SNT snt = snt();
1427+
if (snt == null || !snt.isStreamMode() || !snt.isMaterializedCrop()) return;
1428+
if (snt.getImagePlus() == null) return;
1429+
if (!goToAllPlanes(snt, b, true)) viewer.showViewerMessage("Bookmark is outside the materialized crop");
1430+
}
1431+
1432+
/**
1433+
* Moves the image of the main (XY) view and, if displayed, the side views to a bookmark. Side views are only
1434+
* moved if the bookmark lies within the main view
1435+
*
1436+
* @param silent if true, no error is displayed if the bookmark lies outside the image
1437+
* @return true if the bookmark is within the image (and the displays were moved). False if there is no image
1438+
*/
1439+
private boolean goToAllPlanes(final SNT snt, final Bookmark b, final boolean silent) {
1440+
final ImagePlus imp = snt.getImagePlus();
1441+
if (imp == null || !goTo(b, imp, SNT.XY_PLANE, silent)) return false;
1442+
if (!snt.getSinglePane()) {
1443+
final ImagePlus zyImp = snt.getImagePlus(SNT.ZY_PLANE);
1444+
if (zyImp != null) goTo(b, zyImp, SNT.ZY_PLANE, silent);
1445+
final ImagePlus xzImp = snt.getImagePlus(SNT.XZ_PLANE);
1446+
if (xzImp != null) goTo(b, xzImp, SNT.XZ_PLANE, silent);
1447+
}
1448+
return true;
14201449
}
14211450

14221451
/** Viewer mode: animates the camera to the world position of the selected marker row. */
14231452
private void flyTo(final int row) {
14241453
flyTo(model.getDataList().get(table.convertRowIndexToModel(row)));
14251454
}
14261455

1456+
/**
1457+
* SNT mode: animates every open Bvv/Bdv viewer to the location of the bookmark
1458+
*
1459+
* @return the number of viewers that were moved
1460+
*/
1461+
private int flyOpenViewers(final Bookmark b) {
1462+
if (sntui == null) return 0;
1463+
final java.util.List<AbstractBigViewer> viewers = sntui.getOpenBigViewers();
1464+
for (final AbstractBigViewer v : viewers) v.flyTo(b.getX(), b.getY(), b.getZ(), b.t, b.label);
1465+
return viewers.size();
1466+
}
1467+
14271468
private void flyTo(final Bookmark b) {
14281469
if (viewer == null) return;
14291470
viewer.flyTo(b.getX(), b.getY(), b.getZ(), b.t, b.label);
1471+
goToMaterializedCrop(b); // Stream mode only
14301472
}
14311473

14321474
private void loadBookmarksFromFile(final File file) {

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,14 +676,15 @@ private JPanel buildLiveParamsPanel() {
676676
angleCheck != null && angleCheck.isEnabled());
677677
branchAngleMinCheckbox.setToolTipText("Warns when a child path emerges nearly parallel to its parent");
678678
branchAngleMinSpinner = new JSpinner(new SpinnerNumberModel(
679-
angleCheck != null ? angleCheck.getMinAngleDeg() : 10.0, 0.0, 90.0, 5.0));
679+
angleCheck != null ? angleCheck.getMinAngleDeg() : 10.0, 0.0, 180.0, 5.0));
680680
branchAngleMinCheckbox.addActionListener(e -> {
681681
if (angleCheck != null) angleCheck.setEnabled(
682682
branchAngleMinCheckbox.isSelected() || branchAngleMaxCheckbox.isSelected());
683683
branchAngleMinSpinner.setEnabled(branchAngleMinCheckbox.isSelected());
684684
});
685685
branchAngleMinSpinner.addChangeListener(e -> {
686686
if (angleCheck != null) angleCheck.setMinAngleDeg((Double) branchAngleMinSpinner.getValue());
687+
coupleBranchAngleBounds();
687688
});
688689
// Histogram button: both BranchAngle spinners share the same OUTSIDE-flagged
689690
// distribution. Clicking either button opens the same chart with both markers.
@@ -701,14 +702,15 @@ private JPanel buildLiveParamsPanel() {
701702
angleCheck != null && angleCheck.isEnabled());
702703
branchAngleMaxCheckbox.setToolTipText("Warns when a child path doubles back, nearly anti-parallel to its parent");
703704
branchAngleMaxSpinner = new JSpinner(new SpinnerNumberModel(
704-
angleCheck != null ? angleCheck.getMaxAngleDeg() : 170.0, 90.0, 180.0, 5.0));
705+
angleCheck != null ? angleCheck.getMaxAngleDeg() : 170.0, 0.0, 180.0, 5.0));
705706
branchAngleMaxCheckbox.addActionListener(e -> {
706707
if (angleCheck != null) angleCheck.setEnabled(
707708
branchAngleMinCheckbox.isSelected() || branchAngleMaxCheckbox.isSelected());
708709
branchAngleMaxSpinner.setEnabled(branchAngleMaxCheckbox.isSelected());
709710
});
710711
branchAngleMaxSpinner.addChangeListener(e -> {
711712
if (angleCheck != null) angleCheck.setMaxAngleDeg((Double) branchAngleMaxSpinner.getValue());
713+
coupleBranchAngleBounds();
712714
});
713715
final JButton branchAngleMaxHist = (angleCheck == null) ? null
714716
: CurationHistograms.button("Fork angle", null,
@@ -718,6 +720,7 @@ private JPanel buildLiveParamsPanel() {
718720
() -> ((Number) branchAngleMinSpinner.getValue()).doubleValue(),
719721
CurationHistograms.Side.OUTSIDE_FLAGGED, p);
720722
addCheckRow(p, c, branchAngleMaxCheckbox, spinnerWithHistogram(branchAngleMaxSpinner, branchAngleMaxHist));
723+
coupleBranchAngleBounds();
721724

722725
// Direction continuity
723726
final PlausibilityCheck.DirectionContinuity dirCheck =
@@ -1906,6 +1909,16 @@ private JComponent spinnerWithHistogram(final JSpinner spinner, final JButton hi
19061909
return p;
19071910
}
19081911

1912+
/** Keeps min <= max: each fork angle spinner is bounded by the other's value */
1913+
private void coupleBranchAngleBounds() {
1914+
if (branchAngleMinSpinner == null || branchAngleMaxSpinner == null) return;
1915+
if (branchAngleMinSpinner.getModel() instanceof SpinnerNumberModel minModel
1916+
&& branchAngleMaxSpinner.getModel() instanceof SpinnerNumberModel maxModel) {
1917+
minModel.setMaximum(((Number) maxModel.getValue()).doubleValue());
1918+
maxModel.setMinimum(((Number) minModel.getValue()).doubleValue());
1919+
}
1920+
}
1921+
19091922
/**
19101923
* Reduces a spinner's editor to a compact column count. All curation
19111924
* spinners hold short numeric values (typically 3-5 characters); the

0 commit comments

Comments
 (0)