MicroManager events - core events not coming through?

Code version: Both Git repos pulled on 05 May 2021

Background: I’m working on support for the PureFocus PF-850 autofocus, which needs a device adapter and a setup plugin. We’ve got a load of configuration settings which need saving, so I’m using a config group and putting all the settings in a preset for that config group.

Because of how the PF-850 works, bulk changes of settings need the unit to be ready to accept the changes. The unit has to be stopped from servoing whilst all the changes go in, and then re-enabled when all the settings are done. There don’t seem to be suitable callbacks for this on the device adapter side. However MMCore does produce MMEvents into the Java side, so I looked at whether I could have my plugin register for events and set a “change in progress” property in the device adapter. I was particularly looking for the DefaultConfigGroupChangedEvent event which would give me my “changes are coming” trigger.

Problem: What I’ve found is that these core events aren’t being generated. It’s not just that they aren’t reaching the plugin - I’ve put a breakpoint in the DefaultConfigGroupChangedEvent () constructor and it’s never hit.

Looking into this, I’m suspecting the cause is in MMCore. Breakpointing CMMCore::setConfig(), I can see that being hit when I select a new config group preset, but no callback. Looking through CMMCore for anywhere kicking off the callbacks, the only places I can see which actually produce callbacks are “CMMCore::setChannelGroup()” calling “onChannelGroupChanged” and “CMMCore::loadSystemConfigurationImpl” calling “onSystemConfigurationLoaded”. This ties in with my plugin seeing DefaultSystemConfigurationLoadedEvent but no other core events.

Is it correct that CMMCore is not calling other callbacks, and hence the associated core events are not being produced? Or is there something I’m doing wrong with this?

Or is it just that I shouldn’t be doing this in the first place, and there’s a better solution which I’ve not found yet?

Callbacks are mainly used to give devices the opportunity to signal back to the UI. For instance, the user presses a button on the microscope to change the objective, the adapter calls the OnPropertyChanged callback, which results in several callbacks to the UI (including onPixelSizeChanged, and onConfigGroupChanged). Having UI actions (such as calling core.setConfig) call these callbacks easily leads to endless loops, and the UI already knows that the config group changed… Doubtlessly, there is room for improvement here, but changes here are difficult without inadvertently breaking behavior that many rely on.

In your case, I would entertain the idea of making an “action” property. Setting the parameter properties has no effect (other than that the desired values are stored in the device adapter) until the “action” property (for instance, “Apply settings” with preset “Apply now”, “Settings applied”, and “” as property values). When “Apply now” is set, stop the unit, send all the changes, and start it again, and set the property to “Settings applied”. You could even make read-only properties for all parameters that reflect the actual state of the device rather than the next desired state that can be set in the read-write properties.

Thanks Nico. I wasn’t proposing to use those callbacks directly - I was wanting to have my plugin subscribe to the events (in particular DefaultConfigGroupChangedEvent).

My problem is that the UI as a whole doesn’t know that the config group changed. When another config group preset is selected, the main frame produces a call to the core, but that’s all. MMCore is set up to receive a callback and generate a DefaultConfigGroupChangedEvent event to notify the UI generally - but that callback never comes. So the UI can’t distinguish between bulk changes to properties from a config group and changes to properties from the user typing something in the Property Browser (or a plugin form which sets properties).

I’m already using an “action” property like that. It works fine from my own plugin, of course, because I’m in control of what happens when the user changes a value. I don’t think I can use it with a config group preset though, because I can’t see a way to guarantee the order in which a config group sets properties.

My fallback option of course is to simply disallow changes from config groups in “Configuration settings” or from the Property Browser (because the device adapter can’t tell the difference). From my own plugin form, I can use an “enable” property to allow changes; but from the Property Browser or elsewhere, that “enable” property would not be set and so any changes would simply be ignored.

Would it be acceptable for MM that I force a user to use my device’s plugin to configure/calibrate the device? I can’t see that users would generally want to implement those calibration features themselves, but I don’t want to break some style guide which says they should be allowed to. Of course all more “normal” settings (for autofocus and Z-stacking) will be available as normal; this is only about the configuration/calibration settings.

Sorry if this seems obvious, but I’d rather ask instead of making assumptions about things like this, instead of designing it all to some assumption and then finding it’s not OK to push back.

@marktsuchida, should this be added (to 2.0 I presume)?

@nicost If we are adding this, I think it should be post-2.0. We are in API freeze and I don’t feel that this change is guaranteed to be safe (for the reasons you mentioned).

I do agree with the idea that there needs to be a way to subscribe to changes, though. There is also the inverse problem where if some plugin or script calls setConfig, the main GUI won’t be notified.

@GrahamBartlettPrior I’m not 100% sure this will work for you, but one approach I’ve used (unfortunately not open source) with devices that need their properties set en masse is to bundle the sets based on a timer. This can be done all in the device adapter and therefore keeps things more modular. The device adapter would disable the control loop when any property is set, but after setting the property it sets a timer for e.g. 30 ms, then returns without re-enabling the control loop. If another property is set within the 30 ms, then it resets the timer for another 30 ms. If the 30 ms elapses with no further setting of properties, then the device adapter re-enables the control loop. There is of course the slight disadvantage that you need a background thread for the timed re-enable.

The proper way to support this would be to have an MMCore primitive to set multiple properties at once, of course (falling back to one-by-one setting for devices not supporting it). I did not previously consider it useful to try to add (because existing code, in particular the acquisition engine, wouldn’t use it), but it might be useful given a situation like this.

Thanks Mark. I did consider the timer solution - that’s a fairly standard kluge for Windows programs generally. With so many things talking to hardware with varying response times though, I’m concerned about race conditions and all the other timing issues that could bite us.

My workaround for now is to use a couple of “enable” properties for changing properties in the PureFocus: one to enable changing individual properties during general setup; and one for mass updates on configuration file load. The plugin handles this handshaking, of course, but random changes from the config group selection or the property browser won’t take effect. I can’t see people wanting to do their own thing for configuring the autofocus, not when I’m giving them a plugin GUI for it.

Of course a suitably motivated individual could somewhat interfere with this by playing with the “enable” properties, but I figure if you stick your fingers in the electric socket then you can expect unpleasant results. :slight_smile:

Re events, if you’re thinking about subscribing to changes in config groups more architecturally, then I’d suggest two separate events: one to report that a config group change has been requested; and a second to report that it has completed. Just my 2 cents though. I’m fine with working around this - I just didn’t know if there was something I should have been doing instead and wasn’t.

Thanks @GrahamBartlettPrior. It doesn’t seem like you were missing anything.

I agree about the desirability of two events, although it would come with the caveat that the “before” event might not always fire (if the update comes from a hardware property change notification).