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 aSardanaValue
object) containing an image instead of a scalar valuesardana 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.