pymmcore-plus icon indicating copy to clipboard operation
pymmcore-plus copied to clipboard

Missing XYStagePositionChanged event

Open wl-stepp opened this issue 2 years ago • 9 comments

Hello everyone,

not sure if this is a pymmcore-plus problem or a specific problem with our MCL stage (MCL-μS2081). I don't get XYStagePositionChanged events when moving this stage in from the StageWidget:

from pymmcore_widgets import StageWidget
from pymmcore_plus import CMMCorePlus
from qtpy.QtWidgets import QApplication
from qtpy.QtCore import QObject

mmc = CMMCorePlus()
app = QApplication([])

mmc.loadSystemConfiguration("C:/iSIM/iSIM/mm-configs/pymmcore_plus.cfg")
widget = StageWidget("MicroDrive XY Stage")
widget.show()

class EventReceiver(QObject):
    def __init__(self, mmc):
        super().__init__()
        self.mmc = mmc
        self.mmc.events.XYStagePositionChanged.connect(self.property)

    def property(self, stage, pos):
        print("new_pos", stage, pos)

event_receiver = EventReceiver(mmc)
app.exec_()

It works for the 'XY' stage in the demo config though. When I do the same in Java it receives the events even for the MCL stage.

@Subscribe
public void onXYStagePositionChanged(XYStagePositionChangedEvent event){
sendJSON(event, "Hardware ");
}

https://github.com/LEB-EPFL/pymm-eventserver/blob/629cd55a7762d0eaf5876bc57ec7ea15317ed08e/java/src/main/java/org/micromanager/plugins/pythoneventserver/PythonEventServerFrame.java#L268C1-L272C8

Is there anything known for different behaviors of stages in this case?

pymmcore 10.7.0.71.0 pymmcore-plus 0.8.3 pymmcore-widgets 0.1.dev190+g3f1e2ea C:\iSIM\pymmcore-widgets

wl-stepp avatar Nov 28 '23 09:11 wl-stepp

As a broader point: the general problem of missing events is something we come across often. if I'm not mistaken, micro-manager itself doesn't appear to be designed with events and callbacks as "first class" citizens per se: i believe it's often up to the device adapter implementation to ensure that certain events are emitted (rather than the core itself).

This is why we have things like this in the core:

https://github.com/pymmcore-plus/pymmcore-plus/blob/60623a06da9c5d514e864c00902c188f52ecde07/src/pymmcore_plus/core/_mmcore_plus.py#L2047-L2050

to manually check if something has changed (which of course only works when that specific method has been called from a CMMCorePlus instance).

We also see similar behavior in some stages, which is why we add polling to keep watching/updating the position in the stage widget for example:

https://github.com/pymmcore-plus/pymmcore-widgets/blob/8135e080a2bf9cd493abde4dddf33f647abe9bb8/src/pymmcore_widgets/_stage_widget.py#L163-L168

however that of course doesn't explain why you'd see it in Java, but not in python. So, I need to dig a bit deeper into your java side of things there to see exactly how that subscription is ultimately connect to core, and emitted from core

tlambert03 avatar Nov 28 '23 14:11 tlambert03

I don't get XYStagePositionChanged events when moving this stage in from the StageWidget:

i guess this is the key here. When it "works" on the java side, how exactly are you triggering a move? Are you doing it by clicking a button in an MMStudio widget? If so, then, then it's important to note that the java widget itself emits an event:

https://github.com/micro-manager/micro-manager/blob/9b0229c9ac6f00e1e109440f4d50c9696545ef0b/mmstudio/src/main/java/org/micromanager/internal/navigation/XYNavigator.java#L280-L282

this is essentially a workaround for the unfortunate fact that you can't rely on events coming from device adapters driven by the core... Leaving it up to the caller of the method (in this case the stage widget) to emit the events themselves.

possible solutions here would be:

  • do something similar to what we've done with setProperty, which is to wrap calls to setXYPosition and setRelativeXYPosition with a context manager than ensures that an event is emitted
  • over in pymmcore-widgets, do something similar to what MMStudio does and manually emit the event whenever the stage is moved via the widget

both of these are a bit gross of course, and would incur extra overhead (for properly implemented device adapters that emit the event on their own)... so the "best" fix would be if MMCore itself did something like the first option, and made sure to emit events whenever the stage moves... but that's a deeper design issue in micro-manager. If you'd like to implement any of the above workarounds, let me know

tlambert03 avatar Nov 28 '23 15:11 tlambert03

I see, thanks for the information! I will think about it. My first feeling would be to go for option 1 and wrap the calls, then the event would also be emitted if grid positions are done during an acquisition for example.

wl-stepp avatar Nov 28 '23 15:11 wl-stepp

just chatted with @marktsuchida about this. he expressed support for something like option 1 implemented in CMMCore.

tlambert03 avatar Dec 01 '23 23:12 tlambert03

Just to temper expectations..., it will probably take a few rounds of nontrivial refactoring before we can implement this cleanly in MMCore (i.e., at least months). I hope to get there (those are good refactorings for other goals as well), but it might make pragmatic sense to get it working in pymmcore-plus for the near term.

marktsuchida avatar Dec 04 '23 19:12 marktsuchida

expectations duly tempered :)

tlambert03 avatar Dec 04 '23 19:12 tlambert03

A very crude first try here: #295

wl-stepp avatar Dec 05 '23 13:12 wl-stepp

@wl-stepp, in parallel to #295 (which I see as working towards a more proper solution), there's no reason we can't just do the same workaround/patch that MMStudio's widget does and emit the event when a button is pressed. That should get you something that works the same as Java when you're using the widget for now. Shall we do that?

tlambert03 avatar Jan 08 '24 14:01 tlambert03

Sure, why not. I'm also happy to just have it in my fork for the moment. Seems to be working pretty ok.

wl-stepp avatar Jan 08 '24 15:01 wl-stepp