Python bindings¶
camera_interface lets a Python process own a camera directly: no camerad process, no socket, no
text protocol. Constructing a Camera performs the same one-time setup camerad does at startup,
then the command set is available as methods.
import camera_interface
camera = camera_interface.Camera("hispecatc.cfg")
camera.open()
camera.load()
camera.power("on")
camera.exptime("1.5")
camera.expose("1")
Use it when the caller is the control system, so the text protocol would only be overhead. Keep
camerad when several clients share one camera, or when the camera must outlive the client.
Installing¶
pip install compiles camerad and the module together and puts both in the environment, so
import camera_interface needs no PYTHONPATH and camerad is on PATH:
pip install ./camera-interface \
--config-settings=cmake.define.INSTRUMENT=hispec_tracking_camera
Any CMake option can be passed the same way, so
--config-settings=cmake.define.ENABLE_SHM_OUTPUT=ON works too. CONTROLLER defaults to archon
and BUILD_PYTHON_MODULE is forced on. pybind11 comes from the build requirements into an isolated
build environment and is never installed into the target environment.
A compiler and the full C++ dependency set must be present wherever pip install runs, since it
builds from source.
Important
The controller and instrument are fixed when the wheel is built, and the module is always named
camera_interface, so one environment holds one instrument. Install into a separate environment per
instrument, and have the caller assert which one it got:
assert camera_interface.instrument_name() == "hispec_tracking_camera"
Alternatively cmake -DBUILD_PYTHON_MODULE=ON builds it in the source tree, where importing means
putting the build’s lib directory on PYTHONPATH.
Command coverage¶
Every command camerad accepts is reachable, through three routes:
- Base commands
Bound as methods:
open,close,load,power,exptime,expose,abort,key,datacubeand the rest.- Instrument commands
instrument_cmd(command, args), a deliberate passthrough rather than one binding each, so a build whose instrument gains a command exposes it with no change to the module. Enumerate withinstrument_commands(), or test one withis_instrument_command().- Controller commands
controller_cmd(command, args)formode,raw,readacf,loadtiming,heater,sensorand the other Archon-only commands.
Only exit is missing, since the process belongs to the caller.
Errors¶
A command that fails raises RuntimeError, carrying the server’s own error detail where there is
one:
try:
camera.expose("1")
except RuntimeError as error:
log.error("exposure failed: %s", error)
Commands that succeed return the command’s return string, which is often empty.
Concurrency¶
Blocking commands release the GIL while they run, so a long expose() leaves the rest of the
process responsive. For a daemon that is the difference between one exposure stalling and its whole
RPC loop stalling.
This does not make the object thread-safe. The underlying interface serializes hardware access, but issuing conflicting commands from several threads is still a logic error.
Frame outputs¶
The frame outputs are configured from the .cfg exactly as they are for camerad, and
output_status() reports on them. It returns a list of dicts, one per configured output:
for output in camera.output_status():
print(output["name"], output["frames_written"],
output["frames_dropped"], output["last_written"])
Warning
output_status() is a snapshot, never a barrier. The FITS writer queues and drops frames by design,
and nothing in this API lets a caller stall acquisition by waiting on an output, because that would
serialize acquisition behind disk I/O. Anything that must be woken per frame should attach to the
shared-memory segment, which posts a semaphore per frame.
Logging¶
Logging follows LOG_STDERR from the .cfg, overridable per session with log_to_stderr=:
camera = camera_interface.Camera("hispecatc.cfg", log_to_stderr=True)
The C++ log always goes to its daily file under LOGPATH regardless.
API¶
Direct control of a camera-interface camera, without camerad
- class camera_interface.Camera¶
One camera, configured from a camerad .cfg file.
Construction performs the same setup camerad does at startup: read the config, initialize logging, then configure the controller, interface, instrument and frame outputs. It does not connect to the controller; call open() for that.
- abort(self: camera_interface.Camera, args: str = '') str¶
Abort the exposure in progress
- autodir(self: camera_interface.Camera, args: str = '') str¶
Query whether images go in a dated subdirectory, or set it
- basename(self: camera_interface.Camera, args: str = '') str¶
Query the image base filename, or set it
- bias(self: camera_interface.Camera, args: str = '') str¶
Query a bias voltage, or set it
- bin(self: camera_interface.Camera, args: str = '') str¶
Query the binning factor for an axis, or set it
- close(self: camera_interface.Camera, args: str = '') str¶
Disconnect from the controller
- controller_cmd(self: camera_interface.Camera, command: str, args: str = '') str¶
Run a controller-specific command
- datacube(self: camera_interface.Camera, args: str = '') str¶
Query whether frames are written as a datacube, or set it
- expose(self: camera_interface.Camera, args: str = '') str¶
Take an exposure, or a counted series of them
- exposure_mode(self: camera_interface.Camera, args: str = '') str¶
Query the exposure mode pipeline, or set it
- exposure_modes(self: camera_interface.Camera) list[str]¶
Return the exposure mode names this build supports
- exptime(self: camera_interface.Camera, args: str = '') str¶
Query the exposure time in seconds, or set it
- instrument_cmd(self: camera_interface.Camera, command: str, args: str = '') str¶
Run an instrument-specific command
- instrument_commands(self: camera_interface.Camera) list[str]¶
Return the instrument-specific command names this build supports
- is_instrument_command(self: camera_interface.Camera, command: str) bool¶
Return True if this build’s instrument handles the named command
- key(self: camera_interface.Camera, args: str = '') str¶
Add, list or remove a user FITS header key
- load(self: camera_interface.Camera, args: str = '') str¶
Load firmware, defaulting to DEFAULT_FIRMWARE from the config
- native(self: camera_interface.Camera, args: str) str¶
Send a raw command straight to the controller
- open(self: camera_interface.Camera, args: str = '') str¶
Connect to the controller
- output_status(self: camera_interface.Camera) list¶
Return a per-output snapshot of frames written, dropped, and last file
- power(self: camera_interface.Camera, args: str = '') str¶
Query power state, or set it with “on” or “off”
- test(self: camera_interface.Camera, args: str = '') str¶
Run a named interface test
- camera_interface.controller_name() str¶
Return the controller this module was built for
- camera_interface.instrument_name() str¶
Return the instrument this module was built for
Examples¶
python/examples/shm_read_frames.py is a streaming shared-memory consumer that blocks on the
stream’s semaphore and flags frames it missed. See
frame outputs for what it needs.