micro-manager / mmCoreAndDevices

Micro-Manager's device control layer, written in C++
40 stars 106 forks source link

Generic events from Devices #257

Open henrypinkard opened 2 years ago

henrypinkard commented 2 years ago

As I've moved forward on the prototype for the new camera API (https://github.com/micro-manager/mmCoreAndDevices/issues/243), one thing that has occurred to me is that it would be really useful to make a more generic version of callbacks from devices to the core (a.k.a. "events").

This would lead to several improvements:

  1. It would allow for devices to post device-specific events about updates of their status. I guess the way this is done now is through properties, but this really doesn't cover a lot of cases. For example, if a camera wants to send an ExposureEndEvent, I guess you could do this by changing a property from true and then back to false, but this is ugly. (Not the perfect example because ExposureEndEvent is something we'd ideally like every camera that can to report, but you get the idea)
  2. We could add a mechanism whereby devices declare what events/callbacks they support. As I understand, with the current setup, higher level code just has to wait and see if callbacks come or if they don't, making harder to differentiate bugs where a callback is missed vs an unsupported feature for a particular device. This might also allow the user to turn on and off the posting of different events
  3. It would make easier to add more callbacks/events in the future (whether they are customized to a particular device or standardized in some way)

How to do this

I still think there's something to figure out in terms of how to implement the standardized events. That is, a better mechanism that just having a list of standardized strings that name these events and hoping people use them correctly. Maybe making very lightweight classes for each standardized event in the device layer. This also relates to discussions about standardized properties that we've been having lately (I plan to open an issue on this shortly).

@marktsuchida @nicost what do you think? If you don't see any major reasons to not pursue this, I think I could fairly quickly mock up a prototype in the new camera api branch and see what unexpected issues arise.

marktsuchida commented 2 years ago

I'm not necessarily opposed to the high-level idea of having generic events (although I would like to see some actual use cases first). However:

If you don't see any major reasons to not pursue this

In the form you have it above, at least, I'm really not convinced that it is an improvement over the existing events. It's better to have the event data be expressed as function parameters than an opaque serializedEvent string. Any perceived flexibility from using a serialized string is not actually useful without data schemas on the producing and consuming side, possibly with versioning and whatnot -- but then expressing the fields in C++ is probably easier and definitely simpler. (If both sides need to know the data schema anyway, it is much better to express it in the interface itself.)

There are likely other ways to implement generic events (maybe there could be a struct or class associated with each event), but I'm also not sure that collapsing to a single function, by itself, will improve the current situation (other than, perhaps, the amount of typing needed to add a new event, but that is not something we should be optimizing for). It would seem like you would need something almost as complex as properties, just without the stateful value.

I think I could fairly quickly mock up a prototype in the new camera api branch

It really should be an independent branch that focuses on just this. It's best to keep things small and separated, or it will be hard/impossible to review. Git branches are cheap and pull requests should strive to be single-topic.

(If you want my honest recommendation, I think it is best to avoid having #243 depend on this. I know it's tempting to fix everything, but event callbacks touch a lot more things than trigger settings.)

henrypinkard commented 2 years ago

It's better to have the event data be expressed as function parameters than an opaque serializedEvent string. Any perceived flexibility from using a serialized string is not actually useful without data schemas on the producing and consuming side, possibly with versioning and whatnot -- but then expressing the fields in C++ is probably easier and definitely simpler. (If both sides need to know the data schema anyway, it is much better to express it in the interface itself.)

Good point, I see what you mean and agree.

There are likely other ways to implement generic events (maybe there could be a struct or class associated with each event),

Can this be done in a POD way compatible with the Device-Core interface? If its a generic event, you wouldn't know how many fields it would have, so I would think you'd have to have some serialization mechanism to make it general purpose.

It really should be an independent branch that focuses on just this. It's best to keep things small and separated, or it will be hard/impossible to review. Git branches are cheap and pull requests should strive to be single-topic.

(If you want my honest recommendation, I think it is best to avoid having #243 depend on this. I know it's tempting to fix everything, but event callbacks touch a lot more things than trigger settings.)

I could update the event system in an independent branch as a first step, but I don't see how it could be avoided to have the camera API depend on this. Improvement number 2 mentioned above would seem to be a necessity in order to build a better acquisition engine on top of the new camera API, since not every camera is likely to support every callback/event of the new API

marktsuchida commented 2 years ago

The POD way would be to have an enum constant (event type) together with a pointer to the event-specific struct. Or a function-per-event together with an event-specific struct -- which ends up being similar to what we have.

What I meant was that if I were you, I would try to solve #243 without expanding to a generic event mechanism. But I think I see what you mean, too, and maybe these camera events do indeed need a generic-ish mechanism. I wouldn't be opposed to a single function for "camera timing events" that takes a constant indicating the timing type.

Also it would appear critical that there is an interface to turn on and off the individual events (at the hardware level), given the scary warning about bandwidth and acknowledgment in the Basler docs -- although that could just be boolean properties.

Maybe what I'll say for now is that it might be best to concentrate on the right interface for the camera events, and not try to subsume the existing notification mechanism just yet. We can look at the latter once we know what the camera API will look like.

henrypinkard commented 2 years ago

The POD way would be to have an enum constant (event type) together with a pointer to the event-specific struct. Or a function-per-event together with an event-specific struct -- which ends up being similar to what we have.

I'm still confused about how this could be used in a general way. For example: camera X has some special weird event with its own data fields. We don't have a hard coded struct for it already in MMDevice. One could make a struct for this event type within the device adapter, but then there's no dedicated function for passing that type of struct to the core. So how does the core and other higher level code know fields it contains? Is there some kind of reflection needed? I would think that one would have to pack it into a dictionary-like structure, and (like cameras adding metadata to images) and serialize/deserialize that structure in some a-priori known way.

What I meant was that if I were you, I would try to solve #243 without expanding to a generic event mechanism. But I think I see what you mean, too, and maybe these camera events do indeed need a generic-ish mechanism. I wouldn't be opposed to a single function for "camera timing events" that takes a constant indicating the timing type.

Okay that works. I think it will likely already be generic enough such that if we decide other callbacks should be subsumed into it, it will be straightforward

Also it would appear critical that there is an interface to turn on and off the individual events (at the hardware level), given the scary warning about bandwidth and acknowledgment in the Basler docs -- although that could just be boolean properties.

Yes. Could be property corresponding to each event, or events could be their own class with a required on/off field

marktsuchida commented 2 years ago

I'm still confused about how this could be used in a general way. For example: camera X has some special weird event with its own data fields. We don't have a hard coded struct for it already in MMDevice. One could make a struct for this event type within the device adapter, but then there's no dedicated function for passing that type of struct to the core. So how does the core and other higher level code know fields it contains? Is there some kind of reflection needed? I would think that one would have to pack it into a dictionary-like structure, and (like cameras adding metadata to images) and serialize/deserialize that structure in some a-priori known way.

If you want to support arbitrary events with arbitrary payloads, then indeed, you need a JSON-like (perhaps MsgPack or similar) encoding, or something equally elaborate. Or at least key-value pairs.

henrypinkard commented 2 years ago

Yeah that makes sense

henrypinkard commented 6 months ago

Coming back around to this. Summary of the plan going forward:

One possibility for these latter two is to use standardized properties (https://github.com/micro-manager/mmCoreAndDevices/issues/258), so a reasonable path forward would seem to be to focus on that PR first and then move to this