Skip to content

Commit dfa15fc

Browse files
committed
Fills: Allow export of unique labeled paths
1 parent 68ce4e6 commit dfa15fc

4 files changed

Lines changed: 101 additions & 52 deletions

File tree

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

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public enum State {READY, STARTED, ENDED, LOADED, STOPPED}
9090
private JPopupMenu exportFillsMenu;
9191
private final JCheckBox transparentCheckbox;
9292
private final JCheckBox storeExtraNodesCheckbox;
93+
private final JCheckBox splitFillsCheckbox;
9394

9495

9596
/**
@@ -109,7 +110,7 @@ public FillManagerUI(final SNT plugin) {
109110
listModel = new DefaultListModel<>();
110111
fillList = new JList<>(listModel);
111112
fillList.setCellRenderer(new FMCellRenderer());
112-
fillList.setVisibleRowCount(5);
113+
fillList.setVisibleRowCount(7);
113114
fillList.setPrototypeCellValue(PrototypeFill.instance);
114115
fillList.setComponentPopupMenu(createListPopupMenu());
115116
gUtils = new GuiUtils(this);
@@ -147,16 +148,18 @@ public FillManagerUI(final SNT plugin) {
147148

148149
final JPanel distancePanel = new JPanel(new GridBagLayout());
149150
final GridBagConstraints gdb = GuiUtils.defaultGbc();
150-
cursorThresholdChoice = new JRadioButton("Set by clicking on traced structure (preferred)"); // placeholder default
151+
gdb.insets = new Insets(0, MARGIN, 0, MARGIN); // Consistent left margin
152+
gdb.anchor = GridBagConstraints.LINE_START;
151153

152-
final JPanel t1Panel = leftAlignedPanel();
153-
t1Panel.add(cursorThresholdChoice);
154-
distancePanel.add(t1Panel, gdb);
155-
++gdb.gridy;
154+
// Row 1: Cursor threshold choice
155+
cursorThresholdChoice = new JRadioButton("Set by clicking on traced structure (preferred)");
156+
distancePanel.add(cursorThresholdChoice, gdb);
157+
gdb.gridy++;
156158

159+
// Row 2: Manual threshold with text field and apply button
157160
manualThresholdChoice = new JRadioButton("Specify manually:");
158161
manualThresholdInputField = new JTextField("", 6);
159-
manualThresholdInputField.addActionListener(this); // respond to Enter key
162+
manualThresholdInputField.addActionListener(this);
160163
manualThresholdInputField.addFocusListener(new java.awt.event.FocusAdapter() {
161164
@Override
162165
public void focusGained(java.awt.event.FocusEvent e) {
@@ -167,30 +170,34 @@ public void focusGained(java.awt.event.FocusEvent e) {
167170
});
168171
manualThresholdApplyButton = GuiUtils.Buttons.smallButton("Apply");
169172
manualThresholdApplyButton.addActionListener(this);
170-
final JPanel t2Panel = leftAlignedPanel();
171-
t2Panel.add(manualThresholdChoice);
172-
t2Panel.add(manualThresholdInputField);
173-
t2Panel.add(manualThresholdApplyButton);
174-
distancePanel.add(t2Panel, gdb);
175-
++gdb.gridy;
176173

174+
final JPanel manualPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
175+
manualPanel.add(manualThresholdChoice);
176+
manualPanel.add(manualThresholdInputField);
177+
manualPanel.add(manualThresholdApplyButton);
178+
distancePanel.add(manualPanel, gdb);
179+
gdb.gridy++;
180+
181+
// Row 3: Explored threshold with apply button
177182
exploredThresholdChoice = new JRadioButton("Use explored maximum");
178183
exploredThresholdApplyButton = GuiUtils.Buttons.smallButton("Apply");
179184
exploredThresholdApplyButton.addActionListener(this);
180-
final JPanel t3Panel = leftAlignedPanel();
181-
t3Panel.add(exploredThresholdChoice);
182-
t3Panel.add(exploredThresholdApplyButton);
183-
distancePanel.add(t3Panel, gdb);
184-
++gdb.gridy;
185185

186+
final JPanel exploredPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
187+
exploredPanel.add(exploredThresholdChoice);
188+
exploredPanel.add(exploredThresholdApplyButton);
189+
distancePanel.add(exploredPanel, gdb);
190+
gdb.gridy++;
191+
192+
// Row 4: Defaults button
186193
final JButton defaults = GuiUtils.Buttons.smallButton("Defaults");
187-
defaults.addActionListener( e -> {
194+
defaults.addActionListener(e -> {
188195
plugin.setFillThreshold(-1);
189196
cursorThresholdChoice.setSelected(true);
190197
});
191-
final JPanel defaultsPanel = leftAlignedPanel();
192-
defaultsPanel.add(defaults);
193-
distancePanel.add(defaultsPanel, gdb);
198+
gdb.fill = GridBagConstraints.NONE; // Don't stretch to fill width
199+
distancePanel.add(defaults, gdb);
200+
194201
add(distancePanel, c);
195202
++c.gridy;
196203

@@ -208,21 +215,28 @@ public void focusGained(java.awt.event.FocusEvent e) {
208215

209216
addSeparator(" Performance Impacting Options:", c);
210217

218+
splitFillsCheckbox = new JCheckBox("One fill per path", plugin.getPrefs().getBoolean(SNTPrefs.SPLIT_FILLS_KEY, true));
219+
splitFillsCheckbox.setToolTipText( "When enabled, each path is filled independently, allowing unique labels in exported images.\n" +
220+
"Disable for faster bulk filling of many paths.");
221+
splitFillsCheckbox.addActionListener(e -> plugin.getPrefs().set(SNTPrefs.SPLIT_FILLS_KEY, splitFillsCheckbox.isSelected()));
222+
223+
storeExtraNodesCheckbox = new JCheckBox("Store above-threshold nodes");
224+
storeExtraNodesCheckbox.addActionListener(e -> plugin.setStoreExtraFillNodes(storeExtraNodesCheckbox.isSelected()));
225+
storeExtraNodesCheckbox.setToolTipText("Enabling this option lets you resume progress with the same fill,\nbut may impact performance");
226+
211227
transparentCheckbox = new JCheckBox("Transparent overlay");
212228
transparentCheckbox.setToolTipText("Enabling this option allows you better inspect fills,\nbut may slow down filling");
213229
transparentCheckbox.addActionListener(e -> plugin.setFillTransparent(transparentCheckbox.isSelected()));
214-
final JPanel transparencyPanel = leftAlignedPanel();
215-
transparencyPanel.add(transparentCheckbox);
216-
add(transparencyPanel, c);
230+
231+
final int prevLeftMargin = c.insets.left;
232+
c.insets.left = MARGIN;
233+
add(splitFillsCheckbox, c);
217234
c.gridy++;
218-
storeExtraNodesCheckbox = new JCheckBox(" Store above-threshold nodes");
219-
storeExtraNodesCheckbox.addActionListener(e -> plugin.setStoreExtraFillNodes(storeExtraNodesCheckbox.isSelected()));
220-
storeExtraNodesCheckbox.setToolTipText("Enabling this option lets you resume progress with the same fill,\nbut may impact performance");
221-
final JPanel storeExtraNodesPanel = leftAlignedPanel();
222-
storeExtraNodesPanel.add(storeExtraNodesCheckbox);
223-
add(storeExtraNodesPanel, c);
235+
add(storeExtraNodesCheckbox, c);
224236
c.gridy++;
225-
237+
add(transparentCheckbox, c);
238+
++c.gridy;
239+
c.insets.left = prevLeftMargin;
226240
GuiUtils.addSeparator((JComponent) getContentPane(), " Stored Fill(s):", true, c);
227241
++c.gridy;
228242

@@ -249,7 +263,7 @@ public void focusGained(java.awt.event.FocusEvent e) {
249263
});
250264
reloadFill = new JButton("Reload");
251265
reloadFill.addActionListener(e -> {
252-
if (!noFillsError()) reload("Reload");
266+
if (!noFillsError() && !noValidImgError()) reload("Reload");
253267
});
254268

255269
assembleExportFillsMenu();
@@ -325,8 +339,8 @@ private void setPlaceholderStatusLabels() {
325339
}
326340

327341
protected void updateSettingsString() {
328-
String sb = "Image: " + ((plugin.isTracingOnSecondaryImageActive()) ? "Secondary" : "Main") +
329-
"; Cost func.: " + plugin.getCostType();
342+
final String sb = "<HTML>Image: <i>" + ((plugin.isTracingOnSecondaryImageActive()) ? "Secondary" : "Main") +
343+
"</i> · Cost ƒ(x): <i>" + plugin.getCostType();
330344
fillTypeLabel.setText(sb);
331345
}
332346

@@ -486,6 +500,18 @@ public void setFillTransparent(final boolean transparent) {
486500
plugin.setFillTransparent(transparent);
487501
}
488502

503+
/**
504+
* Sets whether each path should be filled independently.
505+
*
506+
* @param split true to enable "one fill per path", false for bulk filling
507+
*/
508+
public void setSplitFills(final boolean split) {
509+
if (this.splitFillsCheckbox != null) {
510+
SwingUtilities.invokeLater(() -> this.splitFillsCheckbox.setSelected(split));
511+
}
512+
plugin.getPrefs().set(SNTPrefs.SPLIT_FILLS_KEY, split);
513+
}
514+
489515
/* (non-Javadoc)
490516
* @see PathAndFillListener#setPathList(java.lang.String[], Path, boolean)
491517
*/
@@ -643,17 +669,21 @@ public void execute() {
643669
return;
644670
}
645671
try {
672+
final boolean split = plugin.getPrefs().getBoolean(SNTPrefs.SPLIT_FILLS_KEY, splitFillsCheckbox != null
673+
&& splitFillsCheckbox.isSelected());
646674
if (plugin.fillerSet.isEmpty()) {
647675
if (plugin.getUI().getPathManager().selectionExists()) {
648-
plugin.initPathsToFill(new HashSet<>(plugin.getUI().getPathManager().getSelectedPaths(false)));
676+
plugin.initPathsToFill(
677+
new HashSet<>(plugin.getUI().getPathManager().getSelectedPaths(false)), split);
649678
applyCheckboxSelections();
650679
plugin.startFilling();
651680
} else {
652681
final int ans = gUtils.yesNoDialog("There are no paths selected in Path Manager. Would you like to "
653682
+ "fill all paths? Alternatively, you can dismiss this prompt, select a subset in the Path "
654683
+ "Manager list, and rerun. ", "Fill All Paths?", "Yes. Fill All", "No. I'll Select A Subset");
655684
if (ans == JOptionPane.YES_OPTION) {
656-
plugin.initPathsToFill(new HashSet<>(plugin.getUI().getPathManager().getSelectedPaths(true)));
685+
plugin.initPathsToFill(
686+
new HashSet<>(plugin.getUI().getPathManager().getSelectedPaths(true)), split);
657687
applyCheckboxSelections();
658688
plugin.startFilling();
659689
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3648,7 +3648,8 @@ private class FillOutCommand implements PathCommand {
36483648
@Override
36493649
public void execute(List<Path> selectedPaths, String cmd) {
36503650
if (noValidImageDataError()) return;
3651-
plugin.initPathsToFill(new HashSet<>(selectedPaths));
3651+
plugin.initPathsToFill(new HashSet<>(selectedPaths),
3652+
plugin.getPrefs().getBoolean(SNTPrefs.SPLIT_FILLS_KEY, true));
36523653
}
36533654

36543655
@Override

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

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2600,8 +2600,8 @@ public <T extends IntegerType<T>> ImagePlus getFilledLabelImp() {
26002600
final FillConverter converter = new FillConverter(fillerSet);
26012601
converter.convertLabels(out);
26022602
final ImagePlus imp = ImgUtils.raiToImp(out, "Fill");
2603-
imp.copyScale(getImagePlus());
2604-
imp.resetDisplayRange();
2603+
imp.setCalibration(getCalibration());
2604+
imp.setDisplayRange(0, fillerSet.size());
26052605
ImpUtils.setLut(imp, "glasbey_on_dark");
26062606
return imp;
26072607
}
@@ -2637,7 +2637,7 @@ public void addFillerThread(final FillerThread filler) {
26372637
changeUIState(SNTUI.FILLING_PATHS);
26382638
}
26392639

2640-
public synchronized void initPathsToFill(final Set<Path> fromPaths) {
2640+
public synchronized void initPathsToFill(final Set<Path> fromPaths, final boolean splitFillerThreads) {
26412641
fillerSet.clear();
26422642
pathAndFillManager.getLoadedFills().clear();
26432643
final boolean useSecondary = isTracingOnSecondaryImageActive();
@@ -2672,17 +2672,34 @@ public synchronized void initPathsToFill(final Set<Path> fromPaths) {
26722672
default:
26732673
throw new IllegalArgumentException("BUG: Unrecognized cost function " + costType);
26742674
}
2675-
final FillerThread filler = new FillerThread(
2676-
data,
2677-
getCalibration(),
2678-
fillThresholdDistance,
2679-
1000,
2680-
costFunction);
2681-
addThreadToDraw(filler);
2682-
filler.addProgressListener(this);
2683-
if (getUI() != null) filler.addProgressListener(ui.getFillManager());
2684-
filler.setSourcePaths(fromPaths);
2685-
fillerSet.add(filler);
2675+
if (splitFillerThreads) {
2676+
// Create one FillerThread per path for unique labels
2677+
for (final Path path : fromPaths) {
2678+
final FillerThread filler = new FillerThread(
2679+
data,
2680+
getCalibration(),
2681+
fillThresholdDistance,
2682+
1000,
2683+
costFunction);
2684+
addThreadToDraw(filler);
2685+
filler.addProgressListener(this);
2686+
if (getUI() != null) filler.addProgressListener(ui.getFillManager());
2687+
filler.setSourcePaths(Collections.singleton(path)); // One path per filler
2688+
fillerSet.add(filler);
2689+
}
2690+
} else {
2691+
final FillerThread filler = new FillerThread(
2692+
data,
2693+
getCalibration(),
2694+
fillThresholdDistance,
2695+
1000,
2696+
costFunction);
2697+
addThreadToDraw(filler);
2698+
filler.addProgressListener(this);
2699+
if (getUI() != null) filler.addProgressListener(ui.getFillManager());
2700+
filler.setSourcePaths(fromPaths);
2701+
fillerSet.add(filler);
2702+
}
26862703
if (getUI() != null) ui.setFillListVisible(true);
26872704
changeUIState(SNTUI.FILLING_PATHS);
26882705
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class SNTPrefs { // TODO: Adopt PrefService
5252
public static final String RESIZE_REQUIRED = "resizeNeeded";
5353
public static final String RESTORE_LOADED_IMGS = "restoreLoadedImgs";
5454
public static final String AUTOSAVE_KEY = "tracespath";
55+
public static final String SPLIT_FILLS_KEY = "splitfills";
5556

5657
/** Boolean identifiers */
5758
private static final int DRAW_DIAMETERS = 1;

0 commit comments

Comments
 (0)