Documentation of Queued Actions in PYMEAcquire?

I am referring back to the topic on recording camera maps. @DavidBaddeley and @barentine had mentioned the actions that can now be queued in PYMEAcquire.

Coming back to this finally, I had a look at the PYMEAcquire Queued Actions window (see below). I can see that I can load ROIs from a file etc. Overall though I am not 100% certain how to make best use of this interface. @barentine had indicated a number of tricks one could use to implement the camera map recording that were a little too complex for me to get in one go.

Long story short, is there some documentation / examples how to make best use of this facility?

In the meantime I had some success trying this with the simulator, i.e. function definition as below and in the shell of PYMEAcquire just typing:

myaction(scope)

That seems to do the trick. I am happy to do analysis and assembly from tiles offline in the first instance. From your previous comments I get the impression that some clever combination of recipes could do this potentially in real-time.

If there are better ways to achieve all this happy to hear it.

def myaction(scope):
    args = {'state': {'Camera.ROI' : [50, 50, 200, 200]}}
    scope.actions.QueueAction('state.update', args)
    args = {'maxFrames': 500, 'stack': False}
    scope.actions.QueueAction('spoolController.StartSpooling', args)
    args = {'state': {'Camera.ROI' : [100, 100, 250, 250]}}
    scope.actions.QueueAction('state.update', args)
    args = {'maxFrames': 500, 'stack': False}
    scope.actions.QueueAction('spoolController.StartSpooling', args)    
    args = {'state': {'Camera.ROI' : [0, 0, 256, 256]}}
    scope.actions.QueueAction('state.update', args)

Queued actions are pretty new, and as a result largely undocumented and a bit clunky to use. We plan to refine this, but in the meantime what you’ve come up with in your second post is pretty much spot on.

It would be good to hear what you think would constitute a good interface. Current thoughts are to make a more intuitive programatic interface, and/or offer support for loading a set of actions from file. Your specific use case might also be well addressed by a PYMEAcquire plugin or similar which queues actions.

A straw man improvement to the programatic interface would be as follows:

  • In addition to the current string, make QueueAction() accept a callable Action object which would be called with scope as it’s first parameter. Users could subclass Action and over-ride the __call__() method.
  • provide two pre-canned subclasses: actions.StartSpooling and actions.UpdateState
  • also provide a queue_actions() function which takes a list of Action objects.

Using this straw-man interface, your code would look something like:

my_actions = [actions.UpdateState({'Camera.ROI' : [50, 50, 200, 200]}),
              actions.StartSpooling(maxFrames=500, stack=False),
              actions.UpdateState({'Camera.ROI' : [100, 100, 250, 250]}),
              actions.StartSpooling(maxFrames=500, stack=False),
              actions.UpdateState({'Camera.ROI' : [0, 0, 256, 256]})]

scope.actions.queue_actions(my_actions)

Would be great to hear if you think this would be more intuitive.

Thanks for the overview, this makes good sense.

The nicer programming interface would certainly be nice in construction action sets programmatically and also help ensure that the resulting code doesn’t look too arcane for a third party. Action objects seem as described seem nice for that as they bring the interface close to what the actual function interfaces, which they ultimately map to, are. I imagine they would also take nice values and timeouts as part of their kwargs? I haven’t figured out yet how crucial these parameters are, probably mostly as safety net for unsupervised acquisition?

In my use case a PYMEAcquire plugin seems indeed the possibly best option. Can one make external plugins for PYMEAcquire similar to visgui/dsviewer? It seems useful in principle.

Re loading actions from a file, protocol-like action sets may be a good abstraction that get registered when present in a suitable ActionSet folder or similar.

I suppose what constitutes the best mechanism/interface/abstraction is strongly influenced by actual use cases. So far I am aware of tiling for acquisition, the camera chip tiling application for taking maps. Have other ideas come to mind yet?

I think we can (and should) share a single nice value for all actions queued in one queue_actions() call. The envisaged usage for nice is for live cell imaging as follows:

  • take a tiled overview of a whole coverslip / multi-well plate
  • detect cells
  • queue (with a low priority) a bunch of tasks which go from cell to cell taking a widefield (or similar non-damaging image)
  • analyse these images in real time, trying to detect dynamic events (e.g. organelle proximity, cell cycle checkpoint, etc …) where we would want to acquire a super-resolved image (and potentially bleach the cell)
  • queue a series with high priority when we detect such an event

The nice values allow such high priority tasks to push their way to the head of the queue and then for the microscope to return to it’s previously programmed operations once they complete.

Not really sure at this point where the timeouts belong - potentially also on a per queue_actions() basis. The main reason for the timeouts is that the high priority tasks will become irrelevant after a given period of time.

Actually, once we start using the nice values we probably need to wrap our state updates into the other actions, so it would be more like StartSpooling(maxFrames=500, stack=False, state=...), or SpoolSeries(...).

As to PYMEAcquire plugins, they are currently supported through the microscope-specific init.py script. The focusKeys and sarcSpacing modules are good examples of how to implement plugin / or “plugin like” functionality in PYMEAcquire. The calibration queuing could be accomplished by something like the following in your init.py (using the proposed new Action classes so this won’t work just yet):

@init_gui('ROI Calibration)
def roi_calibration(MainFrame, scope):
    def queue_calibration_series(event=None):
        #could also be imported from an external file.
        calib = [actions.SpoolSeries(maxFrames=500, stack=False, 
                                      state={'Camera.ROI' : [50, 50, 200, 200]}),
                 actions.SpoolSeries(maxFrames=500, stack=False,
                                      state={'Camera.ROI' : [100, 100, 250, 250]}),
                 ]

        scope.actions.queue_actions(calib)

    MainFrame.AddMenuItem('Calibration', 'Camera Maps>Sub ROIs', queue_calibration_series)    

Hi @csiscf, the only thing I’ll add is that we have some functionality for linking specific protocols to recipes (if you’re running a cluster[ofone]) - if I remember you were also interested in auto saving camera maps in the right place. At the moment that is all pretty alpha, and the GUI is rubbish (my fault, working on another). Will keep you posted

Thanks @DavidBaddeley, I will try along these lines, knowing that Action objects are yet to come.

@barentine, thanks for the heads up. For now I am perfectly happy to use an offline analysis or dh5view plugin to assemble complete maps. As I find out more about running a cluster[ofone] it would be cool to learn about such a recipes based analysis pipeline if and when its ready for testing.