Skip to content

Commit ac9a8dc

Browse files
authored
Merge pull request micro-manager#2402 from nicost/PFSFocusser
PFSFocusser: Much more robust code. Optional logging.
2 parents fd6883e + ceef9e5 commit ac9a8dc

2 files changed

Lines changed: 100 additions & 5 deletions

File tree

autofocus/src/main/java/org/micromanager/autofocus/OughtaFocus.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,12 @@ public double fullFocus() throws Exception {
200200
core.setAutoShutter(false); // turn off Auto shutter
201201
core.setShutterOpen(true); // open shutter
202202
}
203+
204+
// actually run the autofocus
203205
final double z = focusOptimizer_.runAutofocusAlgorithm();
204206
core.setPosition(zDrive_, z);
207+
208+
// revert settings
205209
if (keepShutterOpen_) { // revert shutter state
206210
core.setAutoShutter(oldAutoShutter);
207211
core.setShutterOpen(oldShutter);

autofocus/src/main/java/org/micromanager/autofocus/PFSOffsetFocusser.java

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,26 +260,110 @@ public double fullFocus() throws Exception {
260260
* @param desiredZPos the Z position in microns for the main zDrive
261261
* @return true on success, false on failure.
262262
*/
263+
// Maximum number of offset-adjustment iterations before we give up. Without
264+
// a cap, a bad (e.g. non-finite) multiplier or an oscillating ZDrive can keep
265+
// this loop running forever, repeatedly commanding the PFS offset.
266+
private static final int MAX_ADJUST_ITERATIONS = 20;
267+
268+
// Set to true to emit verbose per-iteration diagnostics from adjustPFSOffset
269+
// (kept available for future investigation of offset-adjustment issues).
270+
private static final boolean DEBUG_LOGGING = false;
271+
272+
private void debugLog(String message) {
273+
if (DEBUG_LOGGING) {
274+
studio_.logs().logMessage(message);
275+
}
276+
}
277+
263278
private boolean adjustPFSOffset(double desiredZPos) {
264279
CMMCore core = studio_.core();
265280
ArrayList<Pair<Double, Double>> m = new ArrayList<>();
266281
try {
267282
String pixelSizeConfig = core.getCurrentPixelSizeConfig();
268283
MutablePropertyMapView settings = studio_.profile().getSettings(this.getClass());
269-
double multiplier = settings.getDouble(pixelSizeConfig, 1.0);
284+
final double defaultMultiplier = 1.0;
285+
double multiplier = settings.getDouble(pixelSizeConfig, defaultMultiplier);
286+
// A non-finite multiplier may have been persisted by an earlier run.
287+
// Reject it as early as possible and fall back to the default, so the
288+
// bad value can never propagate into the offset calculation.
289+
if (!Double.isFinite(multiplier)) {
290+
studio_.logs().logError("PFSOffsetFocusser: stored multiplier for pixelSizeConfig '"
291+
+ pixelSizeConfig + "' was non-finite (" + multiplier + "); reverting to default "
292+
+ defaultMultiplier + ".");
293+
multiplier = defaultMultiplier;
294+
settings.putDouble(pixelSizeConfig, defaultMultiplier);
295+
}
270296
double zPos = core.getPosition(zDrive_);
297+
debugLog("PFSOffsetFocusser.adjustPFSOffset: start. desiredZPos="
298+
+ desiredZPos + ", initial zPos=" + zPos + ", precision=" + precision_
299+
+ ", pixelSizeConfig=" + pixelSizeConfig + ", stored multiplier=" + multiplier);
300+
int iteration = 0;
271301
while (Math.abs(desiredZPos - zPos) > precision_) {
302+
if (++iteration > MAX_ADJUST_ITERATIONS) {
303+
studio_.logs().logError("PFSOffsetFocusser: gave up adjusting PFS offset after "
304+
+ MAX_ADJUST_ITERATIONS + " iterations; ZDrive did not reach the target "
305+
+ "position (last deviation " + Math.abs(desiredZPos - zPos) + " um).");
306+
return false;
307+
}
272308
double currentOffset = core.getAutoFocusOffset();
273309
double offsetDiff = multiplier * (desiredZPos - zPos);
274-
core.setAutoFocusOffset(currentOffset + offsetDiff);
310+
double newOffset = currentOffset + offsetDiff;
311+
debugLog("PFSOffsetFocusser iter " + iteration
312+
+ ": zPos=" + zPos + ", desiredZPos=" + desiredZPos
313+
+ ", (desiredZPos-zPos)=" + (desiredZPos - zPos)
314+
+ ", multiplier=" + multiplier
315+
+ ", currentOffset=" + currentOffset
316+
+ ", offsetDiff=" + offsetDiff
317+
+ ", newOffset=" + newOffset);
318+
// Never command a non-finite offset to the hardware. This can arise
319+
// if the multiplier became non-finite (see weightedAverage below) or
320+
// if getAutoFocusOffset() returned an unexpected value.
321+
if (!Double.isFinite(newOffset)) {
322+
studio_.logs().logError("PFSOffsetFocusser: computed a non-finite PFS offset ("
323+
+ newOffset + ") from multiplier=" + multiplier + ", currentOffset="
324+
+ currentOffset + ", desiredZPos=" + desiredZPos + ", zPos=" + zPos
325+
+ "; aborting offset adjustment.");
326+
return false;
327+
}
328+
core.setAutoFocusOffset(newOffset);
275329
Thread.sleep(1000);
276330
double newZPos = core.getPosition(zDrive_);
277331
double zPosDiff = newZPos - zPos;
278-
double multiplierEstimate = offsetDiff / zPosDiff;
279-
m.add(new ImmutablePair<>(multiplierEstimate, zPosDiff));
280-
multiplier = weightedAverage(m);
332+
debugLog("PFSOffsetFocusser iter " + iteration
333+
+ ": after setAutoFocusOffset(" + newOffset + "): newZPos=" + newZPos
334+
+ ", zPosDiff=" + zPosDiff);
335+
if (zPosDiff != 0) {
336+
double multiplierEstimate = offsetDiff / zPosDiff;
337+
// Weight by the magnitude of the ZDrive move: larger, more
338+
// reliable moves get more weight. Using the unsigned magnitude
339+
// (rather than signed zPosDiff) prevents the weights from
340+
// cancelling to ~0 when the ZDrive oscillates.
341+
m.add(new ImmutablePair<>(multiplierEstimate, Math.abs(zPosDiff)));
342+
double newMultiplier = weightedAverage(m);
343+
debugLog("PFSOffsetFocusser iter " + iteration
344+
+ ": multiplierEstimate=" + multiplierEstimate
345+
+ ", weight=" + Math.abs(zPosDiff)
346+
+ ", weightedAverage=" + newMultiplier
347+
+ " (sample count=" + m.size() + ")");
348+
// Keep the previous (good) multiplier if the weighted average is
349+
// not usable, e.g. when the weights sum to ~0 and produce a
350+
// divide-by-zero NaN/Infinity.
351+
if (Double.isFinite(newMultiplier)) {
352+
multiplier = newMultiplier;
353+
} else {
354+
studio_.logs().logMessage("PFSOffsetFocusser: weighted-average multiplier was "
355+
+ "non-finite (" + newMultiplier + "); keeping previous multiplier "
356+
+ multiplier + ".");
357+
}
358+
} else {
359+
debugLog("PFSOffsetFocusser iter " + iteration
360+
+ ": zPosDiff is 0; not updating multiplier.");
361+
}
281362
zPos = newZPos;
282363
}
364+
debugLog("PFSOffsetFocusser.adjustPFSOffset: converged after "
365+
+ iteration + " iteration(s); final zPos=" + zPos
366+
+ ", final multiplier=" + multiplier);
283367
settings.putDouble(pixelSizeConfig, multiplier);
284368
} catch (Exception e) {
285369
studio_.logs().logError(e);
@@ -302,6 +386,13 @@ private double weightedAverage(List<Pair<Double, Double>> values) {
302386
sumZPosDiffs += val.getValue();
303387
weightedEstimateSum += val.getValue() * val.getKey();
304388
}
389+
// Weights are non-negative magnitudes (|zPosDiff|), so they only sum to ~0
390+
// if the list is empty or every move was negligible. Guard the divide-by-
391+
// zero and return NaN so the caller can fall back to the previous
392+
// multiplier instead of poisoning the offset.
393+
if (Math.abs(sumZPosDiffs) < 1e-9) {
394+
return Double.NaN;
395+
}
305396
return weightedEstimateSum / sumZPosDiffs;
306397
}
307398

0 commit comments

Comments
 (0)