Coverage for core / src / sensorkit / std / instrument.py: 76%
84 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-02 00:03 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-02 00:03 +0000
1# SPDX-License-Identifier: Apache-2.0
2from datetime import UTC
3from enum import StrEnum
4from typing import Literal
6from pydantic import AwareDatetime, BaseModel
8import sensorkit.api as sk
9from sensorkit.std.traits import TemperatureUnit
12class FrameType(StrEnum):
13 """Astronomical frame type for a camera capture."""
15 LIGHT = "light"
16 BIAS = "bias"
17 DARK = "dark"
18 FLAT = "flat"
21class AcquireData(sk.DeviceCommand):
22 """Control instrument data acquisition.
24 While nothing stops a device from implementing this command directly, typically commands
25 derived from it, such as `CameraCapture`, which add fields and specialize the command
26 semantics for a particular device archetype, are implemented instead.
28 Four distinct actions are defined here, indicated by the `action` field:
30 - *acquire* - Executes a single, self-contained acquisition cycle. This is used for
31 one-shot data collection operations where the device performs a complete measurement
32 and returns the result. The acquisition begins and ends within a single command execution.
33 The command returns when the acquisition is complete.
35 - *start* - Initiates a passive acquisition process. This begins an ongoing operation where
36 the device continuously collects data until explicitly stopped by another `AcquireData`
37 command (graceful), by an `Abort` command (forcible), or an error occurs. The context
38 parameter allows implementation-specific configuration passthrough and provides metadata for
39 data products in the case where the implementation outputs data products continuously. The
40 command returns once the acquisition has successfully begun.
42 - *continue* - Maintains a passive acquisition that was previously started. This action is
43 used to update the acquisition context or to act as a keepalive signal, should those things
44 be supported or required by the device implementation. If a passive collection is not
45 underway, an error is raised. In all cases, this command should return immediately.
47 - *stop* - Ends a passive acquisition that was previously started. If the device writes its
48 output data product once at the end of acquisition, it may support the context parameter in
49 this mode.
51 Note that the storage or transfer of the acquired data is outside the scope of this command.
52 Typically, the device implementing the command will also be the receiver of the acquired data
53 and will write it to a configured `DataGraph`, but it is also possible for data to be handled
54 differently, e.g., received by a different entity or even handled entirely out-of-band of
55 SensorKit.
57 Attributes:
58 action: either "acquire", "start", "continue", or "stop"
59 context: dictionary containing context Keywords
60 """
62 action: Literal["acquire", "start", "continue", "stop"]
63 context: sk.Context | None
66@sk.declare_keyword
67class Binning(BaseModel):
68 """Camera pixel binning configuration.
70 Attributes:
71 x: horizontal binning factor
72 y: vertical binning factor
73 """
75 x: float
76 y: float
79@sk.declare_keyword
80class CameraSensorSize(BaseModel):
81 """Camera sensor physical dimensions in pixels.
83 Attributes:
84 x: sensor width in pixels
85 y: sensor height in pixels
86 """
88 x: int
89 y: int
91 @property
92 def width(self):
93 return self.x
95 @property
96 def height(self):
97 return self.y
100@sk.declare_keyword
101class CameraSensorTemperature(BaseModel):
102 """Camera sensor temperature measurement or setpoint.
104 Attributes:
105 temperature: sensor temperature value
106 units: temperature unit (Fahrenheit, Celsius, or Kelvin)
107 """
109 temperature: float
110 units: TemperatureUnit
113class ConfigureCameraCooler(sk.DeviceCommand):
114 """Set the target temperature of a camera cooler.
116 Attributes:
117 enable: whether the cooler should be enabled
118 setpoint: the target `CameraSensorTemperature`
119 """
121 enable: bool
122 setpoint: CameraSensorTemperature
125class ConfigureCameraSensor(sk.DeviceCommand):
126 """Configure the camera sensor and other settings."""
128 binning: Binning | None = None
129 bias: float | None = None
130 gain: float | None = None
131 readout_mode: int | None = None
134class CameraCapture(AcquireData):
135 """Execute a single camera exposure.
137 Attributes:
138 action: action type for the command (always "acquire")
139 integration_time: exposure time in seconds
140 frame_type: frame type to capture (light, dark, bias, flat)
141 """
143 action: Literal["acquire"] = "acquire"
144 integration_time: float
145 frame_type: FrameType = FrameType.LIGHT
148@sk.declare_keyword
149class ExposureInfo(BaseModel):
150 """Parameters of the exposure that produced an image.
152 Captures the acquisition-time settings and measurements describing *how* an exposure
153 was taken -- the circumstances of the shot as recorded by us. This is distinct from
154 `ImageInfo`, which carries only what is needed to interpret the pixel buffer itself and
155 is therefore recoverable from any image regardless of origin. `ExposureInfo` fields map
156 onto the conventional FITS cards written for a science frame.
158 Attributes:
159 date_obs: Start of the exposure, as a timezone-aware datetime. -> DATE-OBS
160 exposure_time: Actual exposure duration in seconds. -> EXPTIME
161 instrument: Instrument / camera identifier. -> INSTRUME
162 image_type: Frame type (light, dark, bias, flat). -> IMAGETYP
163 readout_mode: Camera readout mode, as an index or a name. -> READOUTM
164 gain: Camera gain. -> GAIN
165 offset: Camera offset / bias level. -> OFFSET
166 ccd_temperature: Sensor temperature during the exposure, in degrees Celsius. -> CCD-TEMP
167 set_temperature: Cooler setpoint, in degrees Celsius. -> SET-TEMP
168 max_adu: Saturation level in ADU under the active readout configuration. -> SATURATE
169 """
171 date_obs: AwareDatetime
172 exposure_time: float
173 instrument: str
174 image_type: FrameType | None = None
175 readout_mode: int | str | None = None
176 gain: float | None = None
177 offset: float | None = None
178 ccd_temperature: float | None = None
179 set_temperature: float | None = None
180 max_adu: int | None = None
182 def get_fits_cards(self):
183 utc = self.date_obs.astimezone(UTC).replace(tzinfo=None)
185 yield "DATE-OBS", (utc.isoformat(), "UTC start of exposure")
186 yield "EXPTIME", (self.exposure_time, "Exposure time [s]")
187 yield "INSTRUME", (self.instrument, "Instrument name")
189 if self.image_type is not None:
190 yield "IMAGETYP", (str(self.image_type), "Frame type")
191 if self.readout_mode is not None:
192 yield "READOUTM", (self.readout_mode, "Readout mode")
193 if self.gain is not None:
194 yield "GAIN", (self.gain, "Camera gain")
195 if self.offset is not None:
196 yield "OFFSET", (self.offset, "Camera offset")
197 if self.ccd_temperature is not None:
198 yield "CCD-TEMP", (self.ccd_temperature, "CCD temperature [C]")
199 if self.set_temperature is not None:
200 yield "SET-TEMP", (self.set_temperature, "Cooler setpoint [C]")
201 if self.max_adu is not None:
202 yield "SATURATE", (self.max_adu, "Saturation level [ADU]")
205StandardCamera = sk.declare_archetype(
206 "camera",
207 required_commands=(CameraCapture,),
208 optional_commands=(ConfigureCameraCooler,),
209)
210"""A standard camera device that can capture images with configurable integration time.
212This archetype defines the interface for camera devices capable of image acquisition
213with specified exposure durations. It supports optional temperature control for
214cooled camera sensors.
215"""
218@sk.declare_keyword
219class RotatorPosition(BaseModel):
220 """Current angular position of the rotator."""
221 position: float
224class ChangeRotatorPosition(sk.DeviceCommand):
225 """Move the rotator to a specified orientation.
227 Attributes:
228 position: target position value for the rotator
229 """
231 position: float
234StandardRotator = sk.declare_archetype(
235 "rotator",
236 required_commands=(ChangeRotatorPosition,),
237)
238"""A device that can rotate an optical element to a specified position.
240This archetype defines the interface for rotator devices that control the orientation of optical
241components such as cameras instrument adapters within an optical system.
242"""