Skip to content

Comments

XY stage position callbacks#850

Closed
ieivanov wants to merge 2 commits intomicro-manager:mainfrom
mehta-lab:fix/xy-stage-position-callbacks
Closed

XY stage position callbacks#850
ieivanov wants to merge 2 commits intomicro-manager:mainfrom
mehta-lab:fix/xy-stage-position-callbacks

Conversation

@ieivanov
Copy link
Contributor

Follow up to #807

In this PR, I'm updating the OnXYStagePositionChanged callback on the CDemoXYStage to report the final position rather than the starting position.

I'm also adding a OnXYStagePositionChanged callback for the ASITiger XY stage, which currently does not emit this callback. I haven't tested this with hardware, but I think it looks like a pretty straightforward change which does not modify the original behavior of relying on CXYStageBase<CXYStage>::SetPositionUm(x, y).

@marktsuchida
Copy link
Member

Perhaps a slight misunderstanding here — the aim of #807 was to avoid notifying the final position at the start of the move (note that the move does not complete during the SetPosition call), because that causes GUI displays to jump to the destination and back to an intermediate point, if the stage already notifies during the move — which I think the Demo stage does, or at least that's what the code looks like.

On the other hand, it's true that ASITiger does not currently notify position changes at all (since #807, like many stages). @nicost Do you think it makes sense for stages to notify their destination at the start of a move, when asynchronous notifications are not immediately available?

Also Cc @jondaniels, @bls337.

@nicost
Copy link
Member

nicost commented Feb 19, 2026

I do not think that any callbacks should be made for devices that do not generate callbacks themselves. After #808 is merged, the calling code can figure out whether or not to expect callbacks from a stage and decide its own strategy how to deal with that.

@nicost
Copy link
Member

nicost commented Feb 19, 2026

I am also confused about the CDemoXYStage additions. It seems to be reporting perfectly for me now. I see the stage updating its position a couple of times, simulating a stage moving with a finite speed, and arriving at the predetermined position.

@jondaniels
Copy link
Contributor

I was not aware of this callback, as it was implemented in the base class and not specifically for ASI. Nor did I know people were using it.

I can see how it is confusing to invoke the callback when SetPositionUm() is called because they stage hasn't actually arrived yet.

Just brainstorming here, how about adding a new method to CXYStageBase that calls in order: SetPositionUm(), WaitForDevice(), and then the callback OnXYStagePositionChanged ?

@ieivanov
Copy link
Contributor Author

Ah, sorry, maybe I've misunderstood the purpose of this callback and how to use it.

In DeviceBase.h we have

   /**
    * @brief Signal that the XY stage has arrived at a new position.
    */
   int OnXYStagePositionChanged(double xPos, double yPos)
   {
      if (callback_)
         return callback_->OnXYStagePositionChanged(this, xPos, yPos);
      return DEVICE_NO_CALLBACK_REGISTERED;
   }

and then again in MMDevice.h

      /**
       * @brief Signal the UI when the XY stage has reached a new position.
       */
      virtual int OnXYStagePositionChanged(const Device* caller, double xPos, double yPos) = 0;

Based on this, it seems to me that OnXYStagePositionChanged should emit once the stage has reached its final destination and report on these coordinates. In CDemoXYStage::SetPositionSteps this call comes after timeOutTimer_ = new MM::TimeoutMs(currentTime, moveDuration_ms_); which made be think that the move is now complete, but I think that's not the case.

Is there a different callback that emits once the stage has reached its final destination after a move?

@jondaniels
Copy link
Contributor

jondaniels commented Feb 20, 2026

AFAIK there isn't a place in the stage device adapter structure where the device adapter knows that a move is complete. As a result, the device adapter cannot emit the OnXYStagePositionChanged callback accurately, and the earlier approach of emitting it when the move starts was confusing because it didn't match the documentation. @nicost or @marktsuchida would be good ones to confirm this understanding.

The calling code normally initiates a stage move (e.g. using SetPositionUm(x, y) and then waits for the move to complete (by polling the device's Busy() (e.g. using WaitForDevice() in MMCore). The device adapter itself doesn't really track whether the move is complete or not, other than implementing the Busy() method (which for ASITiger queries the motor status over serial).

Maybe there could be a bit of code in the Busy() method that could keep a history of the prior return value and invoke the OnXYStagePositionChanged callback when the prior return value was TRUE (i.e. busy) but the current value is FALSE? Just a brainstorm.

@tlambert03
Copy link
Contributor

AFAIK there isn't a place in the stage device adapter structure where the device adapter knows that a move is complete.

yeah, that's my understanding as well, for better or worse. And definitely, many of the device adapters don't follow the behavior here; and therefore it becomes semantically inconsistent to do it, and rely on it, in some devices when you know you can't rely on it in all devices.

Maybe there could be a bit of code in the Busy() method that could keep a history of the prior return value and invoke the OnXYStagePositionChanged callback when the prior return value was TRUE

Yeah, the general challenge of signaling the completion of a move has been left to the application level. for what it's worth, both MMStudio and pymmcore-plus have such a construct.

I can definitely sympathize with the desire of the PR here :) ... but it's hard if not impossible to present this as a device-consistent API

@marktsuchida
Copy link
Member

Agreed with all.

I think what makes the most sense under the current structure is:

  1. Some stages (Zeiss, Leica) receive reliable async position updates from hardware; these should call OnXYStagePositionChanged() to indicate their "(recent) last known position". There is no timing guarantee but it should be "live" from a human perspective.
  2. Other stages (ASI, many serial ones) do not, and would need to poll -- but the application is in a better position to know whether and how frequently to poll.

And as Nico mentioned, #808 will soon give applications (including, e.g., the Stage Control window) better tools for this.

The Demo XY stage simulates type 1 (especially since #597, but confounded by the CXYStageBase-emitted notification until #807). The NotificationTester device adapter can simulate several variations of type 1, as well as type 2.

Is there a different callback that emits once the stage has reached its final destination after a move?

As Jon mentioned, waitForDevice() is for this. We don't have a reliable async notification for motion completion. As a side note (and I like to repeat this at every opportunity), it is important to distinguish between motion completion (indicated by waitForDevice()) and position updates, because there is no guarantee that async position updates will only arrive after the position has settled and stabilized, and also no guarantee that they will ever be within any fixed tolerance interval around the final position. The events are only useful for keeping UIs up to date, not for acquisition sequencing.

I think we should update the OnXYStagePositionChanged() docs to avoid the connotation of "arrived" or "reached". Your comments on #855 would be appreciated. (I might also edit the other notifications later.)

@ieivanov
Copy link
Contributor Author

Fantastic, thanks for clarifying and proving context here. I'll close this PR.

@ieivanov ieivanov closed this Feb 20, 2026
@jondaniels
Copy link
Contributor

For the record, the ASI controller can indicate move completion using a TTL output and this is routinely used in real life (using mode TTL Y=2). If you really need the OnXYStagePositionChanged() call instead of a TTL signal maybe an Arduino device could be made to do it. The connection between the ASI controller and Micro-Manager is all over serial and I cannot think of a way for the controller to send Micro-Manager a "push notification" of stage arrival.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants