micro-manager / mmCoreAndDevices

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

Add a "Clock" device type #195

Open marktsuchida opened 2 years ago

marktsuchida commented 2 years ago

Micro-Manager's hardware-triggered sequencing support has largely assumed that the trigger source (leader) is the camera (running in internal trigger mode), and all other devices are followers.

In many setups, however, it makes more sense to use some other device (e.g. DAQ board) as the leader, and have the camera be just one of the followers.

Such a setup is already quite possible outside of MDA (e.g. in a custom script): one simply needs to start the generation of the trigger sequence (pulse train, "clock") in the DAQ device, after having started the camera in external trigger mode. (Finishing/canceling is more complex than starting, but probably doable.)

But we currently do not have a device interface for devices that generate such trigger sequences. The small number of such devices that we have use non-standard properties to start/stop a trigger sequence. This PR proposes to add this functionality as a new device type called "Clock".

Possible Clock device interface (in addition to general device API):

This is similar to the Camera API stripped down to just the clocking functionality.

Questions:

We should probably think through how we would support concrete devices (Arduino, NIDAQ, ASITiger) and concrete use cases.

Cc: @bls337, @jondaniels, @nicost.

henrypinkard commented 2 years ago

possibly relevant:

https://github.com/micro-manager/futureMMCore/issues/26

henrypinkard commented 2 years ago

pycromanager handles the external leader device through an acquisition hook that allows for that device would be started (see here). Enabling this through the API would be very helpful I think.

Is it possible that this could be implemented as an interface, rather than a distinct device, so that multiple device types could expose the clock functionality? It seems like those 4 functions you mention are quite similar to the camera API

marktsuchida commented 2 years ago

Is it possible that this could be implemented as an interface, rather than a distinct device, so that multiple device types could expose the clock functionality?

Not really. We can't (safely) use multiple inheritance in this API. So the devices would have to provide a separate "clock" object on request, but if putting the interface in a separate object anyway, we might as well stick with the current design (hub with separate clock and other devices) to keep things uniform.

We could just add the clock API to every device, in the same way that every device can have sequenceable properties. But in any case this is flawed, because there are many, e.g., (shutter + state) devices (such as light sources) that could also have clock functionality. Would you add the clock to the shutter or the state device, or both?

Finally, the fact that the API looks similar to the camera API (which is intentional, as I wrote in the original post) does not mean that a camera "is a" clock. We could have had such a design if we started over from scratch, but there is no way we are going to retrofit it.

(This does show that the choice of device types in MMDevice, or the very concept of device types, is somewhat problematic.)

henrypinkard commented 2 years ago

Not really. We can't (safely) use multiple inheritance in this API. So the devices would have to provide a separate "clock" object on request, but if putting the interface in a separate object anyway, we might as well stick with the current design (hub with separate clock and other devices) to keep things uniform.

That makes sense. I'm curious--why would adding multiple inheritance be unsafe. Its something beyond just the fact that it would make the API less uniform since it would be inconsistent with the the hub concept?

marktsuchida commented 2 years ago

It may break the fact that device adapters work across different (Microsoft) compiler versions. It probably won't, but there is no guarantee. Also it may not matter for a long time, now that Microsoft is keeping VS2015-22 binary compatible, but they have not promised anything about the future. There might be other weird effects, because multiple inheritance is complicated, and I would rather not spend time proving that it is safe in our case.

But much more importantly, note that you cannot construct pointers to multiple interfaces. So MMCore can pass around and manipulate MM::Shutter* and MM::ClockMixin*, but not a "shutter that is also a clock" or even "any MM::Device that is also a clock". To be able to convert between different pointer types, the class definition (of the concrete device!) is needed, and that is not available to MMCore. But MM::ClockMixin*, being a mix-in interface, won't have the standard device functions (such as property get/set). So making the device appear to Java like a Java object that implements a Java interface would require a ton of ugly stuff, which defeats the apparent simplicity of multiple inheritance.

If MM::Clock is derived from MM::Device (i.e., not a mix-in class), then we can pass around MM::Clock* and also access its properties, but it doesn't solve the problem of converting to other pointer types or making it work with Java. And it would only work with virtual inheritance, which makes things even more complicated and I don't remember all the consequences of that (I vaguely remember exploring this many years ago, wondering if I could solve the "problem" of shutters and state devices having to be separate devices).