Source code for panoptes.pocs.camera.sbig

from contextlib import suppress

from astropy import units as u
from astropy.io import fits

from panoptes.pocs.camera.sdk import AbstractSDKCamera
from panoptes.pocs.camera.sbigudrv import INVALID_HANDLE_VALUE
from panoptes.pocs.camera.sbigudrv import SBIGDriver
from panoptes.utils.images import fits as fits_utils
from panoptes.utils import error


[docs] class Camera(AbstractSDKCamera): _driver = None _cameras = {} _assigned_cameras = set() def __init__(self, name='SBIG Camera', *args, **kwargs): super().__init__(name, SBIGDriver, *args, **kwargs) self.logger.info('{} initialised'.format(self)) 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('Connecting to {}'.format(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("Could not establish link to {}.".format(self)) self._handle = self._driver.get_driver_handle() if self._handle == INVALID_HANDLE_VALUE: raise error.PanError("Could not connect to {}.".format(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