Architecture¶
How the pieces fit together, and why acquisition is kept separate from output. The C++ reference here is narrative and links to source rather than reproducing signatures.
Layers¶
client (socket, camerad-socksend, or the Python module)
|
Camera::Server ....................... one TCP port, command dispatch, logging
|
Camera::Interface .................... the camera, expressed as commands
| | |
| | +----- FrameOutput .... FITS file, shared memory
| |
| +-------------------- Camera::ExposureMode .... how one exposure runs
|
Camera::Controller ................... the wire to the hardware
|
Archon (TCP) or ARC (PCIe)
Camera::Server(camerad/camera_server.h)Owns the listening socket and the command dispatch chain. Parses a command line, calls the matching
Interfacemethod, appendsDONEorERROR, writes the reply back. Knows nothing about detectors. Each client connection is served on its own thread.Camera::Interface(camerad/camera_interface.h)The abstract camera. Declares one pure virtual per command, and owns the config, the
Camera::Informationfor the current exposure, and the list of frame outputs.ArchonInterfaceandAstroCamInterfaceimplement it for the two controller families; an instrument module subclasses one of those.Camera::ControllerThe transport to the hardware, kept separate from
Interfaceso command semantics and wire protocol vary independently.Camera::ExposureMode(camerad/exposure_modes.h)One way of running an exposure.
ExposureModeTemplate<InterfaceType>is the base each concrete mode derives from; the template parameter gives a mode typed access to its interface. Each mode owns its ownCamera::Informationfor processed and unprocessed images, and anImageProcessorfor deinterlacing.
A single expose means different things for a slow CCD readout and a continuously reading H2RG,
which is exactly what the exposure mode abstracts.
Acquisition: producer and consumer¶
An exposure mode is a producer/consumer pair over a bounded queue:
image_acquisition_thread()The producer. Pulls frames off the controller as fast as the hardware delivers them and enqueues them.
image_processing_thread()The consumer. Dequeues a frame, runs it through the deinterlacer, and hands the result to the frame outputs.
The base class carries the synchronization (queue_mutex, queue_cv, and the
is_producer_finished / is_producer_error / is_consumer_error flags), so a concrete mode
implements only the parts that differ. In the tracking camera module, for instance, a shared base
owns the queue and the consumer and each subclass implements only the producer.
Two lifetimes exist. For a counted exposure the consumer is one-shot: do_expose() spawns it,
and it terminates once the producer is finished and the queue is drained. In freerun, one producer
and one consumer run for the whole session and expose returns immediately, leaving them going
until an abort or a producer error.
The frame path¶
A processed frame is fanned out synchronously to every configured output by
Interface::dispatch_frame(), which simply calls write() on each in turn. When the whole exposure
command finishes, end_exposure() tells each output, so a multi-frame output such as a data cube
can finalize its file.
Outputs are built once at startup by Interface::configure_frame_outputs(), called from
camerad/camerad.cpp for every instrument. It is deliberately not virtual, so no derived
class can silently skip wiring its outputs.
Why outputs never block¶
dispatch_frame() runs on the consumer thread, so anything slow inside an output would stall
acquisition. Each output is therefore required to return promptly, and absorbs the mismatch itself.
The FITS writer is the clearest case (utils/fits_writer.h): write() copies the pixels
into a bounded queue and returns, never touching CCfits. A dedicated worker thread drains that queue
to disk. When the queue is full the oldest frame is dropped. Disk is slower than acquisition can be,
and the design chooses to lose a frame rather than apply backpressure to the detector.
This is also why FrameOutput::status() is documented as a snapshot and never a barrier: letting a
caller wait on an output would serialize acquisition behind disk I/O, reintroducing the coupling the
queue exists to remove.
The shared-memory output makes the opposite trade (utils/shared_memory_writer.cpp): it
copies the frame into the stream buffer and calls ImageStreamIO_UpdateIm, which posts the
semaphores. Nothing it does depends on a reader, so a reader that cannot keep up misses frames but
never delays the writer. Anything that must be woken per frame should attach there rather than watch
for files.
See frame output keys for configuring them.
Instrument modules¶
An instrument is a separate repository, checked out as a submodule under
camerad/Instruments/<name> and selected with -DINSTRUMENT=. It contributes:
A
<name>.cmakefragment settingINSTRUMENT_SOURCES. That is the whole build integration.An
Interfacesubclass deriving fromArchonInterfaceorAstroCamInterface, overriding what the detector needs and adding commands throughinstrument_cmd()andis_instrument_command().Its own
ExposureModeimplementations, where the stock ones do not fit.Optionally a FITS header dictionary and shipped
.cfgand.acffiles.
An interface factory ties it together, so the core builds against the base class and never names a concrete instrument.
Instrument commands are checked before the base command chain, so an instrument can add commands and also override a base one of the same name.
See instruments for the modules that exist, and camerad/Instruments/hispec_tracking_camera for the most complete example.