Skip to content

Commit e383e8d

Browse files
committed
Explorer: Refine Z Autofocus can now exclude edge tiles
1 parent 995f4d8 commit e383e8d

2 files changed

Lines changed: 116 additions & 8 deletions

File tree

plugins/Explorer/src/main/java/org/micromanager/explorer/ExplorerManager.java

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,63 @@ private java.util.List<Tile> collectAcceptedTiles(boolean withinVesselOnly) thro
19891989
return computePositionGrid(withinVesselOnly).tiles;
19901990
}
19911991

1992+
/**
1993+
* Drops tiles within {@code margin} grid tiles of the edge of the occupied region, per well.
1994+
* The edge is followed raggedly: a tile is excluded when it is within {@code margin} of the
1995+
* first/last occupied column in its own grid row OR of the first/last occupied row in its own
1996+
* grid column. This removes the outer ring(s) of tiles - which often only partially overlap the
1997+
* sample and make autofocus fail - while tracking a non-rectangular (curved/diagonal) ROI
1998+
* outline rather than a plain bounding box. Returns the kept tiles (input order preserved).
1999+
*/
2000+
private java.util.List<Tile> excludeEdgeTiles(java.util.List<Tile> tiles, int margin) {
2001+
if (margin <= 0 || tiles.isEmpty()) {
2002+
return new java.util.ArrayList<>(tiles);
2003+
}
2004+
// Per-well occupied extents: for each (well,row) the min/max col, for each (well,col) the
2005+
// min/max row. Non-plate tiles share a single well key of 0.
2006+
java.util.Map<Long, int[]> colRangeByRow = new java.util.HashMap<>(); // {minCol,maxCol}
2007+
java.util.Map<Long, int[]> rowRangeByCol = new java.util.HashMap<>(); // {minRow,maxRow}
2008+
for (Tile t : tiles) {
2009+
long well = wellKeyOf(t);
2010+
long rowKey = (well << 24) ^ (t.row & 0xffffffL);
2011+
long colKey = (well << 24) ^ (t.col & 0xffffffL);
2012+
int[] cr = colRangeByRow.get(rowKey);
2013+
if (cr == null) {
2014+
colRangeByRow.put(rowKey, new int[] {t.col, t.col});
2015+
} else {
2016+
cr[0] = Math.min(cr[0], t.col);
2017+
cr[1] = Math.max(cr[1], t.col);
2018+
}
2019+
int[] rr = rowRangeByCol.get(colKey);
2020+
if (rr == null) {
2021+
rowRangeByCol.put(colKey, new int[] {t.row, t.row});
2022+
} else {
2023+
rr[0] = Math.min(rr[0], t.row);
2024+
rr[1] = Math.max(rr[1], t.row);
2025+
}
2026+
}
2027+
java.util.List<Tile> kept = new java.util.ArrayList<>();
2028+
for (Tile t : tiles) {
2029+
long well = wellKeyOf(t);
2030+
int[] cr = colRangeByRow.get((well << 24) ^ (t.row & 0xffffffL));
2031+
int[] rr = rowRangeByCol.get((well << 24) ^ (t.col & 0xffffffL));
2032+
boolean edge = (t.col - cr[0] < margin) || (cr[1] - t.col < margin)
2033+
|| (t.row - rr[0] < margin) || (rr[1] - t.row < margin);
2034+
if (!edge) {
2035+
kept.add(t);
2036+
}
2037+
}
2038+
return kept;
2039+
}
2040+
2041+
/** Packs a tile's well {row,col} (or 0 for non-plate) into a stable long key. */
2042+
private static long wellKeyOf(Tile t) {
2043+
if (t.well == null) {
2044+
return 0L;
2045+
}
2046+
return ((long) t.well[0] << 32) | (t.well[1] & 0xffffffffL);
2047+
}
2048+
19922049
/**
19932050
* Chooses up to {@code n} tiles TOTAL, spread across the given set using farthest-point sampling
19942051
* in stage space. On multi-well plates the total budget {@code n} is distributed across the
@@ -2003,11 +2060,7 @@ private java.util.List<Tile> chooseSpreadTiles(java.util.List<Tile> tiles, int n
20032060
// Group by well so the budget is spread across wells; non-plate -> single group.
20042061
java.util.Map<Long, java.util.List<Tile>> byWell = new java.util.LinkedHashMap<>();
20052062
for (Tile t : tiles) {
2006-
long key = 0;
2007-
if (t.well != null) {
2008-
key = ((long) t.well[0] << 32) | (t.well[1] & 0xffffffffL);
2009-
}
2010-
byWell.computeIfAbsent(key, k -> new java.util.ArrayList<>()).add(t);
2063+
byWell.computeIfAbsent(wellKeyOf(t), k -> new java.util.ArrayList<>()).add(t);
20112064
}
20122065
int nWells = byWell.size();
20132066
// Distribute n across wells: a base count each, plus one extra to the first 'remainder'
@@ -2778,7 +2831,8 @@ public void cancelRefineZ() {
27782831
* runs the selected autofocus method, and records the resulting Z of every checked Z stage.
27792832
* Runs off the EDT.
27802833
*/
2781-
public void startRefineZAutomatic(int nPoints, String afMethodName, boolean withinVesselOnly) {
2834+
public void startRefineZAutomatic(int nPoints, String afMethodName, boolean withinVesselOnly,
2835+
int edgeMargin) {
27822836
if (dataSource_ == null || !exploring_ || loadedData_ || !hasPositionRoi()) {
27832837
setRefineZStatus("Draw an ROI first.");
27842838
return;
@@ -2798,6 +2852,16 @@ public void startRefineZAutomatic(int nPoints, String afMethodName, boolean with
27982852
setRefineZStatus(ex.getMessage());
27992853
return;
28002854
}
2855+
if (edgeMargin > 0) {
2856+
java.util.List<Tile> interior = excludeEdgeTiles(tiles, edgeMargin);
2857+
// Only apply the exclusion if it leaves something to focus on; otherwise the grid is
2858+
// too thin for the requested margin and we fall back to the full set.
2859+
if (!interior.isEmpty()) {
2860+
tiles = interior;
2861+
} else {
2862+
setRefineZStatus("Edge margin too large for this grid; using all tiles.");
2863+
}
2864+
}
28012865
java.util.List<Tile> chosen = chooseSpreadTiles(tiles, nPoints);
28022866
if (chosen.isEmpty()) {
28032867
setRefineZStatus("No tiles to refine.");

plugins/Explorer/src/main/java/org/micromanager/explorer/RefineZFrame.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.micromanager.Studio;
1717
import org.micromanager.internal.positionlist.utils.ZGenerator;
1818
import org.micromanager.internal.utils.WindowPositioning;
19+
import org.micromanager.propertymap.MutablePropertyMapView;
1920

2021
/**
2122
* Stand-alone window for Refine Z. Opened from the Explorer dialog's "Refine Z..." button so the
@@ -28,15 +29,20 @@
2829
*/
2930
public class RefineZFrame extends JFrame {
3031

32+
private static final String EDGE_MARGIN = "RefineZEdgeMargin";
33+
private static final String AF_METHOD = "RefineZAutofocusMethod";
34+
3135
private final Studio studio_;
3236
private final ExplorerManager explorerManager_;
3337
private final ExplorerFrame explorerFrame_;
38+
private final MutablePropertyMapView settings_;
3439

3540
private final JRadioButton autoRadio_;
3641
private final JRadioButton manualRadio_;
3742
private final JComboBox<ZGenerator.Type> interpCombo_;
3843
private final JComboBox<String> afCombo_;
3944
private final JSpinner pointsSpinner_;
45+
private final JSpinner edgeMarginSpinner_;
4046
private final JButton startButton_;
4147
private final JButton cancelButton_;
4248
private final JButton setButton_;
@@ -62,6 +68,7 @@ public RefineZFrame(Studio studio, ExplorerManager explorerManager,
6268
studio_ = studio;
6369
explorerManager_ = explorerManager;
6470
explorerFrame_ = explorerFrame;
71+
settings_ = studio_.profile().getSettings(RefineZFrame.class);
6572

6673
setTitle("Refine Z");
6774
URL iconUrl = getClass().getResource("/org/micromanager/icons/microscope.gif");
@@ -104,6 +111,7 @@ public RefineZFrame(Studio studio, ExplorerManager explorerManager,
104111
afCombo_.addActionListener(e -> {
105112
Object sel = afCombo_.getSelectedItem();
106113
if (sel != null && !syncingAf_) {
114+
settings_.putString(AF_METHOD, sel.toString());
107115
try {
108116
studio_.getAutofocusManager().setAutofocusMethodByName(sel.toString());
109117
} catch (Exception ex) {
@@ -112,16 +120,29 @@ public RefineZFrame(Studio studio, ExplorerManager explorerManager,
112120
}
113121
});
114122
panel.add(afCombo_);
123+
// Created before the Start button so its action listener can read it (final field).
124+
edgeMarginSpinner_ = new JSpinner(new SpinnerNumberModel(
125+
settings_.getInteger(EDGE_MARGIN, 0), 0, 99, 1));
126+
edgeMarginSpinner_.setToolTipText(
127+
"Exclude reference points within this many tiles of the grid edge. "
128+
+ "Outer tiles often only partially overlap the sample and can fail autofocus. "
129+
+ "0 = use all tiles.");
130+
edgeMarginSpinner_.addChangeListener(e ->
131+
settings_.putInteger(EDGE_MARGIN, (Integer) edgeMarginSpinner_.getValue()));
115132
startButton_ = new JButton("Start");
116133
startButton_.addActionListener(e -> {
117134
Object af = afCombo_.getSelectedItem();
118135
explorerManager_.startRefineZAutomatic(
119136
(Integer) pointsSpinner_.getValue(),
120137
af != null ? af.toString() : null,
121-
explorerFrame_.isWithinVesselSelected());
138+
explorerFrame_.isWithinVesselSelected(),
139+
(Integer) edgeMarginSpinner_.getValue());
122140
});
123141
panel.add(startButton_, "wrap");
124142

143+
panel.add(new JLabel("Exclude edge margin (tiles):"), "split 2");
144+
panel.add(edgeMarginSpinner_, "wrap");
145+
125146
cancelButton_ = new JButton("Cancel");
126147
cancelButton_.setToolTipText("Cancel the running automatic Refine Z");
127148
cancelButton_.addActionListener(e -> explorerManager_.cancelRefineZ());
@@ -155,8 +176,11 @@ public RefineZFrame(Studio studio, ExplorerManager explorerManager,
155176

156177
add(panel, "growx, wrap");
157178

158-
// Populate autofocus methods and select the current interpolation method.
179+
// Populate autofocus methods. Restore the method saved in the profile when it is still
180+
// available, applying it to the main MM window so the two stay in sync; otherwise fall back
181+
// to whatever method MM currently has active.
159182
refreshAfMethods();
183+
restoreSavedAfMethod();
160184
explorerManager_.setRefineZMethod((ZGenerator.Type) interpCombo_.getSelectedItem());
161185

162186
// Re-sync the AF method from the main window each time this window regains focus, since the
@@ -197,6 +221,25 @@ private void refreshAfMethods() {
197221
}
198222
}
199223

224+
/**
225+
* Selects the autofocus method remembered in the profile (if present and still installed) and
226+
* pushes it to the main MM window. Called once at construction so a remembered choice wins over
227+
* MM's current method; later focus-driven {@link #refreshAfMethods()} calls follow MM.
228+
*/
229+
private void restoreSavedAfMethod() {
230+
String saved = settings_.getString(AF_METHOD, null);
231+
if (saved == null || saved.isEmpty()) {
232+
return;
233+
}
234+
for (int i = 0; i < afCombo_.getItemCount(); i++) {
235+
if (saved.equals(afCombo_.getItemAt(i))) {
236+
// Not syncing: we want this selection to propagate to the AutofocusManager.
237+
afCombo_.setSelectedItem(saved);
238+
return;
239+
}
240+
}
241+
}
242+
200243
/** Notifies whether an automatic Refine-Z run is in progress; switches to EDT. */
201244
public void setRefineZRunning(boolean running) {
202245
SwingUtilities.invokeLater(() -> {
@@ -218,6 +261,7 @@ private void updateEnabled() {
218261
autoRadio_.setEnabled(!running_);
219262
manualRadio_.setEnabled(!running_);
220263
pointsSpinner_.setEnabled(auto && !running_);
264+
edgeMarginSpinner_.setEnabled(auto && !running_);
221265
afCombo_.setEnabled(auto && !running_);
222266
startButton_.setEnabled(auto && !running_);
223267
cancelButton_.setEnabled(auto && running_);

0 commit comments

Comments
 (0)