Controls API¶
Quarklib provides high-level access to V4L2 camera controls through a hierarchy of semi-automatically defined types that take care of value conversion and validation.
Finding controls¶
The entry-point to this api is the quarklib.camera.Camera.controls property
available on any quarklib.camera.Camera instance. To print an overview of the
available controls, their formats, and their current values, run the following code
snippet:
from quarklib.camera import Camera
cam = Camera()
print(cam.controls)
The output should look similar to the following:
qtec_default_controls:
digital_gain:
red (scalar, FloatFormatter): 1.0
green (scalar, FloatFormatter): 1.0
blue (scalar, FloatFormatter): 1.0
...
flip:
horizontal (scalar, BoolFormatter): False
vertical (scalar, BoolFormatter): False
binning:
horizontal (scalar, IntFormatter): 1
vertical (scalar, IntFormatter): 1
pattern_generator (scalar, MenuFormatter): Off
To locate a specific control, either find it in the dynamic overview or look at Controls which contains a detailed reference of the full set of controls.
Caution
Depending on your use of camera family and bitstream, all controls may not be
available for your setup. Application code has the responsibility of ensuring that
invalid access is handled appropriately. Accessing a non-existent control results in
an AttributeError.
Using controls¶
All controls inherit from quarklib.controls.IControl which defines an
interface for reading/writing values to that control. Implementations are grouped into
two categories, quarklib.controls.DriverControl and
quarklib.controls.GroupControl. The former covers controls that have a V4L2
equivalent while the latter covers groupings of other controls.
Driver Controls¶
A driver control wraps a specific V4L2 control but provides automatic format conversion
and validation as part of its definition. The
quarklib.controls.DriverControl.formatter attribute, which is an instance of
the quarklib.controls.formatters.AbstractFormatter class, determines what
values are accepted by the control and how they are converted to/from the format
expected by the underlying V4L2 control.
The currently supported formatters are:
quarklib.controls.formatters.IntFormatter: Integer values that may be signed/unsigned and with defined limits.quarklib.controls.formatters.FloatFormatter: Float values that are converted to fixed-point decimal numbers internally.quarklib.controls.formatters.MenuFormatter: An enumeration of strings that map to indices internally.quarklib.controls.formatters.BoolFormatter: A boolean value that maps to an integer internally.
Group Controls¶
Control groups provide high-level and hierarchical access to other controls. Groups contain multiple child controls that can be accessed either through attributes (e.g. cam.controls.digital_gain.red) or through the group’s own read/write methods.
The value type and structure returned from quarklib.controls.GroupControl.read()
and expected as the argument in quarklib.controls.GroupControl.write() depends on
the specific class of group used.
Quarklib provides the following built-in group types (custom ones can be made by
subclassing quarklib.controls.GroupControl):
quarklib.controls.GroupControl: The base type of group allows reading/writing to its children using a dictionary of values.quarklib.controls.GroupArrayControl: Concatenates child values into a single array that can be accessed using the NumPy slicing syntax.quarklib.controls.GroupSplitControl: Writing to this control writes the same value to all its children. Reading returns a dictionary like its parent class.
Value validation¶
All controls perform value validation when writing values. For groups, this is delegated
to its children and for quarklib.controls.DriverControl instances, it is
performed by the type’s quarklib.controls.formatters.AbstractFormatter
instance.
Since validation is performed when calling quarklib.controls.IControl.write(), it
may throw a ValueError. Reading does not need to validate values, since all
written values are assumed to be validated, but may still throw
qamlib.V4L2Exception when IO-related errors occur.
Implementation details¶
The base type is built at runtime when a new quarklib.camera.Camera instance
is created. The exact contents depend on the capabilities of the camera (sensor family,
bitstream, and driver version) but the default configuration using the draco-tokyo
bitstream is documented as quarklib.controls.definitions.QtecDefaultControls.
The main reasons for creating a new class for each control is clarity and for safety. Making a type for each V4L2 control makes it more readily identifiable without having to look at instance variables and it also makes it less likely that controls are used incorrectly since it is possible to specify its type at the usage site. Since Python is dynamically typed, it is of course possible to violate type hints but this is now the responsibility of the caller instead of the callee, thus eliminating the need to verify control formats at each interface point.
The reason the classes are created dynamically is that many of the control parameters can be read from the V4L2 driver itself thus eliminating the need to manually copy these parameters.
Although this approach makes it impossible to inspect the source of each created class, the documentation still provides documentation for generated classes in Controls. These standard control types are created using V4L2 parameters saved from a specific camera configuration (currently the IMX421 with the draco-tokyo bitstream).
Building custom controls¶
You can easily replace the default Qtec control hierarchy in the
quarklib.camera.Camera constructor with a custom
quarklib.controls.GroupControl class. This allows you to build a custom
interface to a camera. This guide will also show you how to extend the default type such
that the resulting Camera instance is still compatible with the rest of the quarklib
API.
Controls are built using the helper classes provided in
quarklib.controls.builders. These each provide a set of class methods that
simplify the creation of new control hierarchies. For examples of how to use them, check
the quarklib.controls.definitions source code.