Skip to content

NikonTI: PFS enabled-state read is stale; enable/disable only takes effect once per session #982

Description

@hinderling

On the original Nikon Ti (NikonTI), the software view of whether the PFS is on drifts out of sync with the hardware. isContinuousFocusEnabled() seems to reflect reality only right after the config loads and then freezes, so it misses both hardware PFS-button presses and software changes. Separately, enableContinuousFocus(true/false) only reaches the hardware the first time per session; later calls are dropped. The only in-session value that stays truthful is the AutoFocus device's Status property, and only when the device object is reconstructed. This looks like the same missing-callback family as #45 (no OnStagePositionChanged) and #826 (XY Busy returns too soon), but for the PFS enabled/status parameter.

Symptom 1: isContinuousFocusEnabled() goes stale

  1. Load the config with the PFS off. isContinuousFocusEnabled() -> false (correct)
  2. Press the PFS button on the microscope body to lock focus.
  3. Call isContinuousFocusEnabled() again -> still false (incorrect)

The same staleness applies to the AutoFocus State property. It never updates in-session, even across an unload/reload of the AutoFocus device, whereas the Status property does update after a reload (see workaround). So the boolean enabled flag and the Status string disagree about the same hardware.

Symptom 2: enableContinuousFocus() only lands once per session

  1. From PFS off, enableContinuousFocus(true) -> PFS engages (correct)
  2. enableContinuousFocus(false) -> PFS disengages (correct)
  3. enableContinuousFocus(true) again -> PFS stays off (incorrect)

Only the first enable and the first disable of a session seem to reach the hardware. Behaves as if the adapter compares the request against its cached enabled flag and then skips the write when they match; since that cached flag is the stale value from Symptom 1, the comparison is against a value that no longer reflects the hardware.

Expected: every enableContinuousFocus() call is honoured, or at least re-checked against the live state rather than a stale cache.

Related: Z moves stall ~10 s when the adapter thinks the PFS is engaged

With the PFS believed to be on, TIZDrive::SetPosition turns the PFS off and then waits ~10 s for a confirmation that never arrives, so every Z-carrying move costs ~10 s. Mentioning it here because a fix to the notification path would likely address all three.

Workarounds

Every symptom points to the PFS enabled/status value being served from a cache that is only updated by a device-change notification, and that notification not being delivered in-process for this param (similar to #45 and #826), reconstructing the device object reads the value fresh, which is why a reload refreshes Status.

Was able to confirm this from the adapter side: With a small binary patch to the adapter .dll that makes the enabled read return the live PFS focus status instead of the cached flag, both symptoms go away: isContinuousFocusEnabled() tracks the hardware, repeated enableContinuousFocus() calls all take effect, as the write-skip is now comparing against a live value.

Without patching the .dll, unloading + and loading the AutoFocus device and then read its Status (Locked in focus / Focusing = engaged). takes around ~60 ms. The stale isContinuousFocusEnabled() / State do not recover even after the reload however, so one solution is to ignore them and rely on Status. Enable/disable latch (Symptom 2) cannot be solved like this however.

Potential solution

Environment

  • Microscope: Nikon Ti-E with PFS, NikonTI device adapter.
  • MMCore version 12.2.2, Device API version 75, Module API version 10. Driven via pymmcore 12.2.2.75.0 and pymmcore-plus 0.18.1; reproduces from plain MMCore.
  • Adapter: mmgr_dal_NikonTI.dll from the Micro-Manager 2.0 api75 nightly, dated 2026-03-22 (seems there were no changes to PFS since then)

Happy to run tests on this setup!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions