Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stage Widget "Snap on Click" -> "Snap after Move" #410

Open
gselzer opened this issue Mar 7, 2025 · 3 comments
Open

Stage Widget "Snap on Click" -> "Snap after Move" #410

gselzer opened this issue Mar 7, 2025 · 3 comments
Assignees

Comments

@gselzer
Copy link
Contributor

gselzer commented Mar 7, 2025

The stage widget has a CheckBox with label "Snap on Click", which differs from mmstudio's "Snap after Move". Some of our folks prefer the latter, as a common goal is "move the stage some amount in some direction, and then snap an image", which aligns more with "Snap after Move".

I was going to change the label, however thinking more about StageWidget._move_stage_relative, this isn't actually the behavior that is implemented; @marktsuchida told me that CMMCore.setRelativePosition does not block, meaning that if you snap on click, you may be snapping an image prior to the move, during the move, even getting motion blur. We should change the behavior to align more with the intentions.

Of course, this goal is easier said than done. StageWidget._move_stage_relative is (currently) called on the GUI thread, so if we want to wait to snap an image we'll have to find some way to wait, ideally without blocking the GUI in the meantime. mmstudio seems to do this by maintaining a map of stages to Runnables - a solution taking inspiration from that architecture may be wise.

And this brings up other fun questions, mainly "what if the user goes crazy on the button?" I assume that right now many stages throw errors on successive calls that are caught here, while others may block the calling thread until the move completes. Our solution should be consistent across devices about:

  1. What happens when a move is initiated and then a second move is requested by another click before the first finishes. We could:
  • Ignore these calls/debounce. This seems most logical to me - naively (and I am naive) if I'm spamming a button like this, I generally just want to keep the stage moving the entire time I'm spamming, and the actual number of times I pressed the button is not terribly important.
  • Queue these calls into a future move. This is what mmstudio's XYNavigator does, and I'm guessing that there may be a reason for this. This solution promotes overscrolling - it could be cancelled with the "STOP" button, but feels like feature misuse. Would be interested in the thoughts of @nicost here, leaning on years of expertise with XYNavigator 😄
  1. When/how many snaps actually occur. If we debounce the behavior would be straightforward, although large pans would become jagged movements. Queueing more likely lends itself to a single snap at the end of all of the queued moves (and this is what mmstudio does, however this likely also promotes overpanning.

All of this is to say that this is an involved fix, and there are likely tradeoffs. cc @tlambert03 @fdrgsp

@tlambert03
Copy link
Member

yep it would be good to snap after move. I think @fdrgsp is doing something like this in the Stage explorer pr as well.

Want to take a stab at something here @gselzer? I'd say let's do the simplest thing first, and not overcomplicate it too much.

I think a handy thing here could be to create a general helper that calls some device method on another thread, uses waitForDevice and returns a python Future ... to which a callback can be connected. If someone hits the button twice, just cancel any existing futures (since we really only need the last one for this particular task)

@gselzer gselzer self-assigned this Mar 10, 2025
@marktsuchida
Copy link

  • Queue these calls into a future move. This is what mmstudio's XYNavigator does, and I'm guessing that there may be a reason for this.

Now that I think about it, I think the reason for this is to allow you to use the buttons to make precise moves in increments of a known distance. If your arrows are set to move 10 µm, in some cases it can be handy to quickly move exactly 30 or 50 µm by clicking 3 or 5 times. It would be frustrating if your clicks got skipped if this is how you model it in your mind.

(If you set (in MMStudio) your different arrows to 1, 10, and 100 µm, you can even move by any 3-digit number of µm of desire. I know that sounds extreme (and not a primary goal of this UI), but I bet somebody has done this.)

I think this kind of thing can really come down to personal preference, and I don't know if one or the other behavior is better in all cases (even for the same person -- it may differ by reason for moving the stage or type of stage (piezo vs motor)). Ultimately it might be best if you could choose one or the other.

And even in the queued case, it's possible to impose an upper limit on how many clicks can be queued, if that turns out to be a danger.

If/when debouncing the button, I think the snap-after should still wait until the stage is quiescent. Or at least that option should be available. This is because this feature is strongly motivated by the desire to minimize photobleaching of the specimen (in comparison to Live view). (Perhaps this is an argument against the debounced behavior itself, because you'd need to wait something like 200 ms after motion completes to tell if the user is still multiple-clicking.)

@tlambert03
Copy link
Member

I do think the behavior of clicking it 3 times quickly to get a 3x relative move is nice. So this means maintaining an internal “target” position, independent of the core, and adjusting accordingly.

There is a lot of special casing here. I can try to have a look at this soon

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

No branches or pull requests

3 participants