Source code for panoptes.pocs.camera.sbig

"""SBIG camera driver implementation backed by the SBIG Universal Driver.

Provides a Camera class that uses the ctypes-based SBIGDriver to control cooled
SBIG CCD/CMOS cameras and integrate with the AbstractSDKCamera interface.
"""

from contextlib import suppress

from astropy.io import fits

from panoptes.utils import error

from panoptes.pocs.camera.sbigudrv import INVALID_HANDLE_VALUE, SBIGDriver
from panoptes.pocs.camera.sdk import AbstractSDKCamera


[docs] class Camera(AbstractSDKCamera): """SBIG camera implementation using the SBIG Universal Driver (sbigudrv). Wraps SBIGDriver calls to provide cooling, exposure, and readout control consistent with AbstractSDKCamera. """ _driver = None _cameras = {} _assigned_cameras = set() def __init__(self, name="SBIG Camera", *args, **kwargs): super().__init__(name, SBIGDriver, *args, **kwargs) self.logger.info(f"{self} initialised") def __del__(self): with suppress(AttributeError): self._driver.set_handle(INVALID_HANDLE_VALUE) # Shortcut to closing device & driver self.logger.debug("Closed SBIG camera device & driver") super().__del__() # Properties @property def egain(self): """Image sensor gain in e-/ADU as reported by the camera.""" return self.properties["readout modes"]["RM_1X1"]["gain"] @property def temperature(self): """ Current temperature of the camera's image sensor. """ temp_status = self._driver.query_temp_status(self._handle) return temp_status["imaging_ccd_temperature"] @AbstractSDKCamera.target_temperature.getter def target_temperature(self): """ Current value of the target temperature for the camera's image sensor cooling control. Can be set by assigning an astropy.units.Quantity. """ temp_status = self._driver.query_temp_status(self._handle) return temp_status["ccd_set_point"] @AbstractSDKCamera.cooling_enabled.getter def cooling_enabled(self): """ Current status of the camera's image sensor cooling system (enabled/disabled). Can be set by assigning a bool. """ temp_status = self._driver.query_temp_status(self._handle) return temp_status["cooling_enabled"] @property def cooling_power(self): """ Current power level of the camera's image sensor cooling system (as a percentage of the maximum). """ temp_status = self._driver.query_temp_status(self._handle) return temp_status["imaging_ccd_power"] @property def is_exposing(self): """True if an exposure is currently under way, otherwise False""" return self._driver.get_exposure_status(self._handle) == "CS_INTEGRATING" # Methods
[docs] def connect(self): """ Connect to SBIG camera. Gets a 'handle', serial number and specs/capabilities from the driver """ self.logger.debug(f"Connecting to {self}") # This will close device and driver, ensuring it is ready to access a new camera self._driver.set_handle(handle=INVALID_HANDLE_VALUE) self._driver.open_driver() self._driver.open_device(self._address) self._driver.establish_link() link_status = self._driver.get_link_status() if not link_status["established"]: raise error.PanError(f"Could not establish link to {self}.") self._handle = self._driver.get_driver_handle() if self._handle == INVALID_HANDLE_VALUE: raise error.PanError(f"Could not connect to {self}.") self._info = self._driver.get_ccd_info(self._handle) self.model = self.properties["camera name"] # No way to directly ask the camera whether it has image sensor cooling or not. Need to # check camera type and infer from that. As far as I can tell all models apart from the # ST-i range and the SG-4 (which isn't included in the SDK yet) have cooling. if self.properties["camera type"] != "STI_CAMERA": self._is_cooled_camera = True if self.properties["colour"]: if self.properties["Truesense"]: self._filter_type = "CRGB" else: self._filter_type = "RGGB" else: self._filter_type = "M" # Stop camera from skipping lowering of Vdd for exposures of 3 seconds of less self._driver.disable_vdd_optimized(self._handle) self._connected = True if self.filterwheel and not self.filterwheel.is_connected: # Need to defer connection of SBIG filter wheels until after camera is connected # so do it here. self.filterwheel.connect()
# Private methods def _set_target_temperature(self, target): self._driver.set_temp_regulation(self._handle, target, self.cooling_enabled) self._target_temperature = target def _set_cooling_enabled(self, enable): target = self.target_temperature self._driver.set_temp_regulation(self._handle, target, enable) def _start_exposure(self, seconds, filename, dark, header, *args, **kwargs): readout_mode = "RM_1X1" # Unbinned mode top = 0 # Unwindowed too left = 0 height = self.properties["readout modes"][readout_mode]["height"] width = self.properties["readout modes"][readout_mode]["width"] self._driver.start_exposure( handle=self._handle, seconds=seconds, dark=dark, antiblooming=self.properties["imaging ABG"], readout_mode=readout_mode, top=top, left=left, height=height, width=width, ) readout_args = (filename, readout_mode, top, left, height, width, header) return readout_args def _readout(self, filename, readout_mode, top, left, height, width, header): exposure_status = self._driver.get_exposure_status(self._handle) if exposure_status == "CS_INTEGRATION_COMPLETE": try: image_data = self._driver.readout(self._handle, readout_mode, top, left, height, width) except RuntimeError as err: raise error.PanError(f"Readout error on {self}, {err}") else: self.write_fits(data=image_data, header=header, filename=filename) elif exposure_status == "CS_IDLE": raise error.PanError(f"Exposure missing on {self}") else: raise error.PanError(f"Unexpected exposure status on {self}: '{exposure_status}'") def _create_fits_header(self, seconds, dark=None, metadata=None) -> fits.Header: header = super()._create_fits_header(seconds, dark) # Unbinned. Need to chance if binning gets implemented. readout_mode = "RM_1X1" header.set("CAM-FW", self.properties["firmware version"], "Camera firmware version") header.set("XPIXSZ", self.properties["readout modes"][readout_mode]["pixel width"].value, "Microns") header.set( "YPIXSZ", self.properties["readout modes"][readout_mode]["pixel height"].value, "Microns", ) return header