Skip to content

Commit e831533

Browse files
committed
Extend NotesUI to stream mode and improve startup prompts
1 parent 422955e commit e831533

4 files changed

Lines changed: 161 additions & 29 deletions

File tree

src/main/java/sc/fiji/snt/NotesUI.java

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@
2222

2323
package sc.fiji.snt;
2424

25+
import ij.ImagePlus;
2526
import sc.fiji.snt.gui.GuiUtils;
2627
import sc.fiji.snt.gui.IconFactory;
2728
import sc.fiji.snt.gui.SNTEditorPane;
29+
import sc.fiji.snt.viewer.AbstractBigViewer;
2830

2931
import javax.swing.*;
3032
import java.awt.*;
33+
import java.awt.geom.Rectangle2D;
3134
import java.io.File;
3235
import java.io.FileWriter;
3336
import java.io.IOException;
37+
import java.util.Collection;
3438

3539
/**
3640
* Implements the <i>Notepad</i> pane.
@@ -39,6 +43,8 @@
3943
*/
4044
public class NotesUI {
4145

46+
static { net.imagej.patcher.LegacyInjector.preinit(); } // required for _every_ class that imports ij. classes
47+
4248
private final SNTUI sntui;
4349
private final SNTEditorPane editor;
4450

@@ -121,11 +127,13 @@ private JComponent getToolBar() {
121127
final String cheatsheet = markDownOverview();
122128
if (editor.getText().contains(cheatsheet)) {
123129
editor.setText(editor.getText().replace(cheatsheet, ""));
130+
editor.requestFocusInWindow();
124131
} else {
132+
final int cheatsheetStart = editor.getDocument().getLength() + 1;
125133
editor.append(cheatsheet);
126-
editor.setCaretPosition(editor.getDocument().getLength()-cheatsheet.length());
134+
editor.setCaretPosition(cheatsheetStart);
135+
scrollToOffsetIfNotVisible(cheatsheetStart + cheatsheet.length()/2); // scroll to middle
127136
}
128-
editor.requestFocusInWindow();
129137
});
130138
final JToolBar toolbar = new JToolBar();
131139
toolbar.add(open);
@@ -137,24 +145,29 @@ private JComponent getToolBar() {
137145
toolbar.addSeparator();
138146
toolbar.add(editor.timeStampButton(e -> {
139147
editor.appendTimeStamp("*", "*\n");
140-
editor.requestFocusInWindow();
148+
scrollToOffsetIfNotVisible(editor.getDocument().getLength());
141149
}));
142150
final JButton settingsStamp = new JButton(IconFactory.buttonIcon('\uf2db', true, IconFactory.defaultColor()));
143151
settingsStamp.setToolTipText("Insert computation settings");
144152
settingsStamp.addActionListener(e -> {
145153
editor.append("**Computation Settings:**");
146154
editor.append("\n```\n" + computationSettings() +"\n```\n");
147-
editor.requestFocusInWindow();
155+
scrollToOffsetIfNotVisible(editor.getDocument().getLength());
148156
});
149157
toolbar.add(settingsStamp);
150158
final JButton imgTitleStamp = new JButton(IconFactory.buttonIcon('\uf03e', true, IconFactory.defaultColor()));
151-
imgTitleStamp.setToolTipText("Insert name of image being traced");
159+
imgTitleStamp.setToolTipText("Insert details on image being traced");
152160
imgTitleStamp.addActionListener(e -> {
153161
if (!sntui.plugin.accessToValidImageData()) {
154162
sntui.noValidImageDataError();
155163
} else {
156-
editor.append("`" + sntui.plugin.getImagePlus().getTitle() + "`\n");
157-
editor.requestFocusInWindow();
164+
if (sntui.plugin.getImagePlus() != null)
165+
editor.append("`" + standardModeImageDetails(sntui.plugin.getImagePlus()) + "`\n");
166+
else if (sntui.plugin.isStreamMode())
167+
editor.append("`" + streamModeImageDetails() + "`\n");
168+
else
169+
editor.append("`unknown image title`\n");
170+
scrollToOffsetIfNotVisible(editor.getDocument().getLength());
158171
}
159172
});
160173
toolbar.add(imgTitleStamp);
@@ -166,7 +179,7 @@ private JComponent getToolBar() {
166179
sntui.error("Current tracings do not seem to be associated with a TRACES file.");
167180
} else {
168181
editor.append("`" + file.getName() + "`\n");
169-
editor.requestFocusInWindow();
182+
scrollToOffsetIfNotVisible(editor.getDocument().getLength());
170183
}
171184
});
172185
toolbar.add(filenameStamp);
@@ -208,6 +221,61 @@ public String computationSettings() {
208221
return sntui.geSettingsString();
209222
}
210223

224+
private String streamModeImageDetails() {
225+
final SNT p = sntui.plugin;
226+
final StringBuilder sb = new StringBuilder("Streamed data");
227+
if (p.getWidth() > 0 && p.getHeight() > 0 && p.getDepth() > 0) {
228+
sb.append(String.format(" (%dx%dx%d px", p.getWidth(), p.getHeight(), p.getDepth()));
229+
if (p.getPixelWidth() > 0 && p.getPixelHeight() > 0 && p.getPixelDepth() > 0) {
230+
sb.append(String.format("; %.3fx%.3fx%.3f %s/px", p.getPixelWidth(), p.getPixelHeight(),
231+
p.getPixelDepth(), p.getSpacingUnits()));
232+
}
233+
sb.append(")");
234+
}
235+
if (p.isMaterializedCrop()) sb.append(" [materialized crop]");
236+
final AbstractBigViewer viewer = sntui.getActiveBigViewer();
237+
if (viewer != null) {
238+
final String sourcePath = viewer.getPrimarySourcePath();
239+
if (sourcePath != null && !sourcePath.isBlank()) sb.append(", source: ").append(sourcePath);
240+
if (!viewer.getRenderedTrees().isEmpty())
241+
sb.append(", ").append(viewer.getRenderedTrees().size()).append(" tree(s)");
242+
if (viewer.hasMarkerManager() && viewer.getMarkerManager().hasBookmarks())
243+
sb.append(", ").append(viewer.getMarkerManager().getCount()).append(" marker(s)");
244+
}
245+
return sb.toString();
246+
}
247+
248+
private String standardModeImageDetails(final ImagePlus imp) {
249+
assert imp != null;
250+
final StringBuilder sb = new StringBuilder(imp.getTitle());
251+
sb.append(String.format(" (%dx%dx%d px", imp.getWidth(), imp.getHeight(), imp.getNSlices()));
252+
final ij.measure.Calibration cal = imp.getCalibration();
253+
if (cal != null && cal.pixelWidth > 0 && cal.pixelHeight > 0) {
254+
sb.append(String.format("; %.3fx%.3fx%.3f %s/px", cal.pixelWidth, cal.pixelHeight,
255+
(cal.pixelDepth > 0) ? cal.pixelDepth : 1d, cal.getUnit()));
256+
}
257+
sb.append(")");
258+
if (imp.getNChannels() > 1) sb.append(", ").append(imp.getNChannels()).append(" channels");
259+
if (imp.getNFrames() > 1) sb.append(", ").append(imp.getNFrames()).append(" frames");
260+
final String sourcePath = standardModeSourcePath(imp);
261+
if (sourcePath != null) sb.append(", source: ").append(sourcePath);
262+
final Collection<Tree> trees = sntui.plugin.getPathAndFillManager().getTrees();
263+
if (trees != null && !trees.isEmpty()) sb.append(", ").append(trees.size()).append(" tree(s)");
264+
if (sntui.getBookmarkManager().hasBookmarks())
265+
sb.append(", ").append(sntui.getBookmarkManager().getCount()).append(" marker(s)");
266+
return sb.toString();
267+
}
268+
269+
/** Best-effort original file path/URL for {@code imp}, or null if unknown. */
270+
private String standardModeSourcePath(final ImagePlus imp) {
271+
final ij.io.FileInfo fi = imp.getOriginalFileInfo();
272+
if (fi == null) return null;
273+
if (fi.url != null && !fi.url.isBlank()) return fi.url;
274+
if (fi.directory != null && fi.fileName != null && !fi.fileName.isBlank())
275+
return fi.directory + fi.fileName;
276+
return null;
277+
}
278+
211279
private void loadNotesFromFile(final File file) {
212280
try {
213281
editor.open(file);
@@ -232,6 +300,23 @@ private boolean noNotesError() {
232300
return blank;
233301
}
234302

303+
private void scrollToOffsetIfNotVisible(final int offset) {
304+
SwingUtilities.invokeLater(() -> {
305+
try {
306+
final Rectangle2D r2d = editor.modelToView2D(offset);
307+
if (r2d == null) return;
308+
final Rectangle rect = r2d.getBounds();
309+
if (!editor.getVisibleRect().contains(rect)) {
310+
editor.scrollRectToVisible(rect);
311+
}
312+
} catch (final javax.swing.text.BadLocationException ignored) {
313+
// offset no longer valid (e.g., editor content changed in the meantime): nothing to scroll to
314+
} finally {
315+
editor.requestFocusInWindow();
316+
}
317+
});
318+
}
319+
235320
private void noRecordComment() {
236321
if (null != sntui.getRecorder(false))
237322
sntui.getRecorder(false).recordComment("Notepad actions are currently not recorded");

src/main/java/sc/fiji/snt/gui/cmds/BigDataLoaderCmd.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ and big data formats with lazy loading (N5, Zarr, HDF5, OME-TIFF,
8989

9090

9191
@Parameter(required = false, visibility= ItemVisibility.MESSAGE, persist = false)
92-
String msgHeader= "<HTML>All files can be specified by either local paths or remote URLs. Only <i>Main volume</i> is a mandatory field.";
92+
String msgHeader= "<HTML>This is the initialization prompt for TB-sized images (w/ optional tracing via <i>SNT " +
93+
"Stream</i>). For regular, in-memory<br>" +
94+
"images, use the <i>Startup...</i> option instead. All files can be specified by either local paths or " +
95+
"remote URLs. Only<br>" +
96+
"the <i>Main volume</i> field is mandatory.";
9397

9498
// NB: persist = false on all four File parameters below: SciJava's own generic File-parameter persistence restores
9599
// a value via `new File(persistedString)`, which mangles a remote URL (see toPathString()). Persistence is instead
@@ -122,13 +126,15 @@ and big data formats with lazy loading (N5, Zarr, HDF5, OME-TIFF,
122126
private static final String REC_KEY = "recFiles";
123127
private static final String MARKER_KEY = "markerFile";
124128

125-
@Parameter(label = "Viewer type", description = "The type of viewer.\nTracing capabilities are provided by SNT Stream.",
126-
choices = {
127-
"Big Data Viewer (BDV): Interactive reslicing",
128-
"Big Data Viewer (BDV): Interactive reslicing w/ tracing capabilities",
129-
"Big Volume Viewer (BVV): 3D rendering",
130-
"Big Volume Viewer (BVV): 3D rendering w/ tracing capabilities"})
131-
String viewerChoice;
129+
@Parameter(label = "Viewer type", description = "The type of viewer.",
130+
choices = {"Big Data Viewer (BDV): Interactive reslicing", "Big Volume Viewer (BVV): 3D rendering"})
131+
String viewerType;
132+
133+
@Parameter(label = "Enable tracing (SNT Stream)",
134+
description = "<HTML>If enabled, the viewer becomes the main tracing canvas w/ tracing capabilities.<br>"
135+
+ "If disabled, a plain, lightweight viewer is opened with no SNT session attached.",
136+
callback = "tracingEnabledChanged")
137+
boolean tracingEnabled = true;
132138

133139
@Parameter(required = false, visibility= ItemVisibility.MESSAGE, persist = false)
134140
String msg;
@@ -143,16 +149,28 @@ private void loadDemo() {
143149
img2File = null;
144150
recFiles = new File("https://raw.githubusercontent.com/morphonets/misc/680ac2a9b2cb1dfe85c0b64f17fed816e3da1647/dataset-demos/marmoset_neurons/autotracings.traces");;
145151
markerFile = new File("https://raw.githubusercontent.com/morphonets/misc/680ac2a9b2cb1dfe85c0b64f17fed816e3da1647/dataset-demos/marmoset_neurons/soma_detections.csv");
146-
viewerChoice = "Big Data Viewer (BDV): Interactive reslicing w/ tracing capabilities";
152+
viewerType = "Big Data Viewer (BDV): Interactive reslicing";
153+
tracingEnabled = true;
147154
}
148155

149156
@SuppressWarnings("unused")
150157
private void init() {
151-
msg = (SNTUtils.getInstance() == null)
152-
? "" : "<HTML>NB: <i>SNT Stream</i> requires SNT to not already be running. Please close the active instance first.";
158+
updateRunningInstanceWarning();
153159
populateLastUsed();
154160
}
155161

162+
@SuppressWarnings("unused")
163+
private void tracingEnabledChanged() {
164+
updateRunningInstanceWarning();
165+
}
166+
167+
private void updateRunningInstanceWarning() {
168+
msg = (tracingEnabled && SNTUtils.getInstance() != null)
169+
? "<HTML>NB: Enabling tracing (<i>SNT Stream</i>) requires SNT to not already be running. "
170+
+ "Please close the active instance first,<br>or disable tracing above."
171+
: "";
172+
}
173+
156174
/** Restores the four File fields from PrefService, in place of SciJava's own (URL-mangling) persistence. */
157175
private void populateLastUsed() {
158176
final String lastImg1 = prefService.get(BigDataLoaderCmd.class, IMG1_KEY);
@@ -194,9 +212,8 @@ public void run() {
194212
error("No volume files specified.");
195213
return;
196214
}
197-
final boolean threeD = viewerChoice == null || viewerChoice.toLowerCase().contains("bvv")
198-
|| viewerChoice.toLowerCase().contains("vol");
199-
final boolean tracer = viewerChoice != null && viewerChoice.toLowerCase().contains("tracing");
215+
final boolean threeD = viewerType != null && viewerType.toLowerCase().contains("bvv");
216+
final boolean tracer = tracingEnabled;
200217

201218
if (tracer && SNTUtils.getInstance() != null) {
202219
error("SNT seems to be already running. Please close the current instance and re-run.");

src/main/java/sc/fiji/snt/gui/cmds/SNTLoaderCmd.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,35 @@ public class SNTLoaderCmd extends DynamicCommand {
6969
private static final String DEF_DESCRIPTION =
7070
"Optional. Ignored when a display canvas is used";
7171

72+
@Parameter(required = false, visibility= ItemVisibility.MESSAGE, persist = false)
73+
String msgHeader= "<HTML>This is SNT's initialization prompt for regular, in-memory images. " +
74+
"For TB-sized data<br>use the <i>Big Data...</i> option. All fields are optional.";
75+
7276
@Parameter(required = true, label = "Image", //
73-
description = "The image to be traced (optional). If binary, it will be eligible for automated reconstruction.", //
77+
description = "<HTML>Optional. The image to be traced.", //
7478
callback = "imageChoiceChanged")
7579
private String imageChoice;
7680

7781
@Parameter(required = false, label = "Image file",
78-
description = "<HTML>Image file, when <i>Image</i> choice is <i>"+ IMAGE_FILE +"</i> (optional)",
82+
description = "<HTML>Optional. Image file when <i>Image</i> choice is<br><i>"+ IMAGE_FILE +"</i>",
7983
style = FileWidget.OPEN_STYLE, callback = "imageFileChanged")
8084
private File imageFile;
8185

82-
@Parameter(required = false, label = "<HTML>&nbsp;",
83-
visibility = ItemVisibility.MESSAGE)
86+
@Parameter(required = false, label = "<HTML>&nbsp;", visibility = ItemVisibility.MESSAGE)
8487
private String SPACER1;
8588

8689
@Parameter(required = false, label = "Reconstruction file", //
87-
description="The reconstruction file to be loaded: JSON, NDF, SWC, or TRACES (optional)",
90+
description="<HTML>Optional. The reconstruction file to be loaded:<br>" +
91+
"TRACES, SWC, Neurolucida XML, NDF, JSON...",
8892
style = FileWidget.OPEN_STYLE, callback = "tracesFileChanged")
8993
private File tracesFile;
9094

9195
@Parameter(required = false, label = "<HTML>&nbsp;",
9296
visibility = ItemVisibility.MESSAGE)
9397
private String SPACER2;
9498

95-
@Parameter(required = false, label = "User interface", choices = { UI_DEFAULT,
96-
UI_SIMPLE }, description = DEF_DESCRIPTION + " or image is 2D")
99+
@Parameter(required = false, label = "User interface", choices = { UI_DEFAULT, UI_SIMPLE },
100+
description = DEF_DESCRIPTION + " or image is 2D")
97101
private String uiChoice;
98102

99103
@Parameter(required = false, label = "Tracing channel",

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,16 @@ public sc.fiji.snt.BookmarkManager getMarkerManager() {
630630
return markerManager;
631631
}
632632

633+
/**
634+
* Checks whether a {@link sc.fiji.snt.BookmarkManager} has already been created for this
635+
* viewer, without triggering its (lazy, non-trivial) creation as {@link #getMarkerManager()}
636+
* would. Useful for display/logging code that wants to report marker counts only if the
637+
* panel is already in use.
638+
*/
639+
public boolean hasMarkerManager() {
640+
return markerManager != null;
641+
}
642+
633643

634644
/**
635645
* Sets the voxel calibration for the viewer.
@@ -667,6 +677,22 @@ public String getCalUnit() {
667677
return calUnit;
668678
}
669679

680+
/**
681+
* Returns the file path/URL of the primary loaded volume, for display/logging purposes
682+
* (e.g., a Notes entry documenting the dataset being traced).
683+
* <p>
684+
* Only sources registered through {@link #spimDataFilePaths} (i.e., datasets opened via
685+
* {@code AbstractSpimData}-based {@code show(...)} overloads, such as N5/Zarr/BDV/IMS data)
686+
* are tracked. There is no guaranteed order if more than one source is loaded; this simply
687+
* returns the first entry found.
688+
* </p>
689+
*
690+
* @return the source path/URL, or null if unknown/not applicable
691+
*/
692+
public String getPrimarySourcePath() {
693+
return spimDataFilePaths.values().stream().findFirst().orElse(null);
694+
}
695+
670696
/**
671697
* Derives the best available physical unit string.
672698
* Subclasses may override to add viewer-specific fallbacks

0 commit comments

Comments
 (0)