Source code for panoptes.pocs.camera.fli

from contextlib import suppress

import numpy as np

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

from panoptes.pocs.camera.sdk import AbstractSDKCamera
from panoptes.pocs.camera.libfli import FLIDriver
from panoptes.pocs.camera import libfliconstants as c
from panoptes.utils import error


[docs] class Camera(AbstractSDKCamera): _driver = None _cameras = {} _assigned_cameras = set() def __init__(self, name='FLI Camera', target_temperature=25 * u.Celsius, *args, **kwargs): kwargs['target_temperature'] = target_temperature super().__init__(name, FLIDriver, *args, **kwargs) self.logger.info('{} initialised'.format(self)) def __del__(self): with suppress(AttributeError): handle = self._handle self._driver.FLIClose(handle) self.logger.debug('Closed FLI camera handle {}'.format(handle.value)) super().__del__() # Properties @property def temperature(self): """ Current temperature of the camera's image sensor. """ return self._driver.FLIGetTemperature(self._handle) @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. """ return self._target_temperature @property def cooling_enabled(self): """ Current status of the camera's image sensor cooling system (enabled/disabled). Note: For FLI cameras this is always True, and cannot be set. """ return True @cooling_enabled.setter def cooling_enabled(self, enable): # Cooling is always enabled on FLI cameras if not enable: raise error.NotSupported("Cannot disable cooling on {}".format(self.name)) @property def cooling_power(self): """ Current power level of the camera's image sensor cooling system (as a percentage of the maximum). """ return self._driver.FLIGetCoolerPower(self._handle) @property def is_exposing(self): """ True if an exposure is currently under way, otherwise False """ return bool(self._driver.FLIGetExposureStatus(self._handle).value) # Methods
[docs] def connect(self): """ Connect to FLI camera. Gets a 'handle', serial number and specs/capabilities from the driver """ self.logger.debug('Connecting to {}'.format(self)) self._handle = self._driver.FLIOpen(port=self._address) if self._handle == c.FLI_INVALID_DEVICE: message = 'Could not connect to {} on {}!'.format(self.name, self._camera_address) raise error.PanError(message) self._get_camera_info() self.model = self.properties['camera model'] # All FLI camera models are cooled self._is_cooled_camera = True self._connected = True
# Private Methods def _set_target_temperature(self, target): self._driver.FLISetTemperature(self._handle, target) # Check for success? self._target_temperature = target def _set_cooling_enabled(): raise NotImplementedError def _start_exposure(self, seconds, filename, dark, header, *args, **kwargs): self._driver.FLISetExposureTime(self._handle, exposure_time=seconds) if dark: frame_type = c.FLI_FRAME_TYPE_DARK else: frame_type = c.FLI_FRAME_TYPE_NORMAL self._driver.FLISetFrameType(self._handle, frame_type) # For now set to 'visible' (i.e. light sensitive) area of image sensor. # Can later use this for windowed exposures. self._driver.FLISetImageArea(self._handle, self.properties['visible corners'][0], self.properties['visible corners'][1]) # No on chip binning for now. self._driver.FLISetHBin(self._handle, bin_factor=1) self._driver.FLISetVBin(self._handle, bin_factor=1) # No pre-exposure image sensor flushing, either. self._driver.FLISetNFlushes(self._handle, n_flushes=0) # In principle can set bit depth here (16 or 8 bit) but most FLI cameras don't support it. # Start exposure self._driver.FLIExposeFrame(self._handle) readout_args = (filename, self.properties['visible width'], self.properties['visible height'], header) return readout_args def _readout(self, filename, width, height, header): # Use FLIGrabRow for now at least because I can't get FLIGrabFrame to work. # image_data = self._FLIDriver.FLIGrabFrame(self._handle, width, height) image_data = np.zeros((height, width), dtype=np.uint16) rows_got = 0 try: for i in range(image_data.shape[0]): image_data[i] = self._driver.FLIGrabRow(self._handle, image_data.shape[1]) rows_got += 1 except RuntimeError as err: message = 'Readout error on {}, expected {} rows, got {}: {}'.format( self, image_data.shape[0], rows_got, err) raise error.PanError(message) else: self.write_fits(data=image_data, header=header, filename=filename) def _create_fits_header(self, seconds, dark=None, metadata=None) -> fits.Header: header = super()._create_fits_header(seconds, dark) header.set('CAM-HW', self.properties['hardware version'], 'Camera hardware version') header.set('CAM-FW', self.properties['firmware version'], 'Camera firmware version') header.set('XPIXSZ', self.properties['pixel width'].value, 'Microns') header.set('YPIXSZ', self.properties['pixel height'].value, 'Microns') return header def _get_camera_info(self): serial_number = self._driver.FLIGetSerialString(self._handle) camera_model = self._driver.FLIGetModel(self._handle) hardware_version = self._driver.FLIGetHWRevision(self._handle) firmware_version = self._driver.FLIGetFWRevision(self._handle) pixel_width, pixel_height = self._driver.FLIGetPixelSize(self._handle) ccd_corners = self._driver.FLIGetArrayArea(self._handle) visible_corners = self._driver.FLIGetVisibleArea(self._handle) self._info = { 'serial number': serial_number, 'camera model': camera_model, 'hardware version': hardware_version, 'firmware version': firmware_version, 'pixel width': pixel_width, 'pixel height': pixel_height, 'array corners': ccd_corners, 'array height': ccd_corners[1][1] - ccd_corners[0][1], 'array width': ccd_corners[1][0] - ccd_corners[0][0], 'visible corners': visible_corners, 'visible height': visible_corners[1][1] - visible_corners[0][1], 'visible width': visible_corners[1][0] - visible_corners[0][0] }