How to write a 2D controller

This chapter provides the necessary information to write a two dimensional (2D) experimental channel controller in Sardana.

General guide

2D experimental channels together with 1D experimental channels and counter/timers belong to the same family of timerable experimental channels.

To write a 2D controller class you can follow the How to write a counter/timer controller guide keeping in mind differences explained in continuation.

Get 2D shape

2D controller should provide a shape of the image which will be produced by acquisition. The shape can be either static e.g. defined by the detector’s sensor size or dynamic e.g. depending on the detector’s (or an intermediate control software layer e.g. LImA) configuration like RoI or binning.

In any case you should provide the shape in the format of a two-element sequence with horizontal and vertical dimensions using the GetAxisPar() method.

Here is an example of the possible implementation of GetAxisPar():

class SpringfieldTwoDController(TwoDController):

    def GetAxisPar(self, axis, par):
        if par == "shape":
            return self.springfield.getShape(axis)

For backwards compatibility, in case of not implementing the shape axis parameter, shape will be determined frm the MaxDimSize of the Value attribute, currently (4096, 4096).

Differences with counter/timer controller

Class definition

The basics of the counter/timer controller chapter explains how to define the counter/timer controller class. Here you need to simply inherit from the TwoDController class:

from sardana.pool.controller import TwoDController

class SpringfieldTwoDController(TwoDController):

   def __init__(self, inst, props, *args, **kwargs):
        super().__init__(inst, props, *args, **kwargs)

Get 2D value

Get counter value chapter explains how to read a counter/timer value using the ReadOne() method. Here you need to implement the same method but:

  • its return value must be a two-dimensional numpy.array (or eventually a SardanaValue object) containing an image instead of a scalar value

  • sardana will not call ReadOne() at a given frequency during the acquisition to get the still changing result

Get 2D values

Get counter values chapter explains how to read counter/timer values using the ReadOne() method while acquiring with external (hardware) synchronization. Here you need to implement the same method but its return value must be a sequence with two-dimensional numpy.array objects (or eventually with SardanaValue objects) containing the images instead of a scalar values.

Advanced topics

Working with value referencing

2D experimental channels may produce big arrays of data at high frame rate. Reading this data and storing it using sardana is not always optimal. SEP2 introduced data saving duality, optionally, leaving the data storage at the responsibility of the detector (or an intermediate software layer e.g. LImA). In this case sardana just deals with the reference to the data.

In order to announce the referencing capability the 2D controller must additionally inherit from the Referable class:

from sardana.pool.controller import TwoDController, Referable

class SpringfieldTwoDController(TwoDController, Referable):

    def __init__(self, inst, props, *args, **kwargs):
        super().__init__(inst, props, *args, **kwargs)

Get 2D value reference

To get the 2D value reference, sardana calls the RefOne() method. This method receives an axis as parameter and should return a URI (str) pointing to the value.

Here is an example of the possible implementation of RefOne():

class SpringfieldTwoDController(TwoDController):

    def RefOne(self, axis):
        value_ref = self.springfield.getValueRef(axis)
        return value_ref

Get 2D values references

Get counter values chapter explains how to read counter/timer values using the ReadOne() method while acquiring with external (hardware) synchronization. Here you need to implement the RefOne() method and its return value must be a sequence with URIs (str) pointing to the values.

Configure 2D value reference

Two axis parameters: value_ref_pattern (str) and value_ref_enabled (bool) are foreseen for configuring where to store the values and whether to use the value referencing. Here you need to implement the SetAxisPar() method.

Here is an example of the possible implementation of SetAxisPar():

class SpringfieldTwoDController(TwoDController):

    def SetAxisPar(self, axis, par, value):
        if par == "value_ref_pattern":
            self.springfield.setValueRefPattern(axis, value)
        elif par == "value_ref_enabled":
            self.springfield.setValueRefEnabled(axis, value)

Hint

Use Python Format String Syntax e.g. file:///tmp/sample1_{index:02d} to configure a dynamic value referencing using the acquisition index or any other parameter (acquisition index can be reset in the per measurement preparation. phase)

When value referencing is used

Sardana will Get 2D value reference when:

  • channel has referencing capability and it is enabled

Sardana will Get 2D value when any of these conditions applies:

  • channel does not have referencing capability

  • channel has referencing capability but it is disabled

  • there is a pseudo counter based on this channel

Hence, in some configurations, both methods may be used simultaneously.