Missing XYStagePositionChanged event
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
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
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 tosetXYPositionandsetRelativeXYPositionwith 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
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.
just chatted with @marktsuchida about this. he expressed support for something like option 1 implemented in CMMCore.
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.
expectations duly tempered :)
A very crude first try here: #295
@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?
Sure, why not. I'm also happy to just have it in my fork for the moment. Seems to be working pretty ok.