def __init__(self, index=0, **kwargs): super().__init__(index=index, **kwargs) if not AndorSDK3.SDK_INITIALIZED: SDK3.InitialiseLibrary() self.handle = None # self._sdk3cam = SDK3Camera(self._index) # SDK3Camera.__init__(self, self._index) self.add_setting( "use_callback", "bool", lambda: self._using_callback, self._enable_callback, None, ) # Define features with local style. The SDK treats parameter names # without regard to case, so we just need to remove the underscores # when connecting properties to SDK calls. We define all possible # features here; they will be removed if they are not implemented # on the camera. self._accumulate_count = ATInt() self._acquisition_start = ATCommand() self._acquisition_stop = ATCommand() self._alternating_readout_direction = ATBool() self._aoi_binning = ATEnum() self._aoi_height = ATInt() self._aoi_left = ATInt() self._aoi_top = ATInt() self._aoi_width = ATInt() self._aoi_stride = ATInt() self._auxiliary_out_source = ATEnum() self._aux_out_source_two = ATEnum() self._baseline_level = ATInt() self._bit_depth = ATEnum() self._buffer_overflow_event = ATInt() self._bytes_per_pixel = ATFloat() self._camera_acquiring = ATBool() self._camera_dump = ATCommand() self._camera_model = ATString() self._camera_name = ATString() self._camera_present = ATBool() self._controller_id = ATString() self._frame_count = ATInt() self._cycle_mode = ATEnum() self._electronic_shuttering_mode = ATEnum() self._event_enable = ATBool() self._events_missed_event = ATInt() self._event_selector = ATEnum() self._exposed_pixel_height = ATInt() self._exposure_time = ATFloat() self._exposure_end_event = ATInt() self._exposure_start_event = ATInt() self._external_trigger_delay = ATFloat() self._fan_speed = ATEnum() self._firmware_version = ATString() self._frame_rate = ATFloat() self._full_aoi_control = ATBool() self._image_size_bytes = ATInt() self._interface_type = ATString() self._io_invert = ATBool() self._io_selector = ATEnum() self._line_scan_speed = ATFloat() self._lut_index = ATInt() self._lut_value = ATInt() self._max_interface_transfer_rate = ATFloat() self._metadata_enable = ATBool() self._metadata_timestamp = ATBool() self._metadata_frame = ATBool() self._overlap = ATBool() self._pixel_encoding = ATEnum() self._pixel_readout_rate = ATEnum() self._pre_amp_gain_control = ATEnum() self._readout_time = ATFloat() self._rolling_shutter_global_clear = ATBool() self._row_n_exposure_end_event = ATInt() self._row_n_exposure_start_event = ATInt() self._row_read_time = ATFloat() self._scan_speed_control_enable = ATBool() self._sensor_cooling = ATBool() self._sensor_height = ATInt() self._sensor_readout_mode = ATEnum() self._sensor_temperature = ATFloat() self._sensor_width = ATInt() self._serial_number = ATString() self._simple_pre_amp_gain_control = ATEnum() self._software_trigger = ATCommand() self._static_blemish_correction = ATBool() self._spurious_noise_filter = ATBool() self._target_sensor_temperature = ATFloat() self._temperature_control = ATEnum() self._temperature_status = ATEnum() self._timestamp_clock = ATInt() self._timestamp_clock_frequency = ATInt() self._timestamp_clock_reset = ATCommand() self._trigger_mode = ATEnum() self._vertically_centre_aoi = ATBool() # Software buffers and parameters for data conversion. self.num_buffers = 32 self.add_setting( "num_buffers", "int", lambda: self.num_buffers, lambda val: self.set_num_buffers(val), lambda: (1, 100), ) self.buffers = queue.Queue() self._buffer_size = None self._img_stride = None self._img_width = None self._img_height = None self._img_encoding = None self._buffers_valid = False self._exposure_callback = None self.initialize()
class AndorSDK3( microscope.abc.FloatingDeviceMixin, microscope.abc.Camera, ): SDK_INITIALIZED = False def __init__(self, index=0, **kwargs): super().__init__(index=index, **kwargs) if not AndorSDK3.SDK_INITIALIZED: SDK3.InitialiseLibrary() self.handle = None # self._sdk3cam = SDK3Camera(self._index) # SDK3Camera.__init__(self, self._index) self.add_setting( "use_callback", "bool", lambda: self._using_callback, self._enable_callback, None, ) # Define features with local style. The SDK treats parameter names # without regard to case, so we just need to remove the underscores # when connecting properties to SDK calls. We define all possible # features here; they will be removed if they are not implemented # on the camera. self._accumulate_count = ATInt() self._acquisition_start = ATCommand() self._acquisition_stop = ATCommand() self._alternating_readout_direction = ATBool() self._aoi_binning = ATEnum() self._aoi_height = ATInt() self._aoi_left = ATInt() self._aoi_top = ATInt() self._aoi_width = ATInt() self._aoi_stride = ATInt() self._auxiliary_out_source = ATEnum() self._aux_out_source_two = ATEnum() self._baseline_level = ATInt() self._bit_depth = ATEnum() self._buffer_overflow_event = ATInt() self._bytes_per_pixel = ATFloat() self._camera_acquiring = ATBool() self._camera_dump = ATCommand() self._camera_model = ATString() self._camera_name = ATString() self._camera_present = ATBool() self._controller_id = ATString() self._frame_count = ATInt() self._cycle_mode = ATEnum() self._electronic_shuttering_mode = ATEnum() self._event_enable = ATBool() self._events_missed_event = ATInt() self._event_selector = ATEnum() self._exposed_pixel_height = ATInt() self._exposure_time = ATFloat() self._exposure_end_event = ATInt() self._exposure_start_event = ATInt() self._external_trigger_delay = ATFloat() self._fan_speed = ATEnum() self._firmware_version = ATString() self._frame_rate = ATFloat() self._full_aoi_control = ATBool() self._image_size_bytes = ATInt() self._interface_type = ATString() self._io_invert = ATBool() self._io_selector = ATEnum() self._line_scan_speed = ATFloat() self._lut_index = ATInt() self._lut_value = ATInt() self._max_interface_transfer_rate = ATFloat() self._metadata_enable = ATBool() self._metadata_timestamp = ATBool() self._metadata_frame = ATBool() self._overlap = ATBool() self._pixel_encoding = ATEnum() self._pixel_readout_rate = ATEnum() self._pre_amp_gain_control = ATEnum() self._readout_time = ATFloat() self._rolling_shutter_global_clear = ATBool() self._row_n_exposure_end_event = ATInt() self._row_n_exposure_start_event = ATInt() self._row_read_time = ATFloat() self._scan_speed_control_enable = ATBool() self._sensor_cooling = ATBool() self._sensor_height = ATInt() self._sensor_readout_mode = ATEnum() self._sensor_temperature = ATFloat() self._sensor_width = ATInt() self._serial_number = ATString() self._simple_pre_amp_gain_control = ATEnum() self._software_trigger = ATCommand() self._static_blemish_correction = ATBool() self._spurious_noise_filter = ATBool() self._target_sensor_temperature = ATFloat() self._temperature_control = ATEnum() self._temperature_status = ATEnum() self._timestamp_clock = ATInt() self._timestamp_clock_frequency = ATInt() self._timestamp_clock_reset = ATCommand() self._trigger_mode = ATEnum() self._vertically_centre_aoi = ATBool() # Software buffers and parameters for data conversion. self.num_buffers = 32 self.add_setting( "num_buffers", "int", lambda: self.num_buffers, lambda val: self.set_num_buffers(val), lambda: (1, 100), ) self.buffers = queue.Queue() self._buffer_size = None self._img_stride = None self._img_width = None self._img_height = None self._img_encoding = None self._buffers_valid = False self._exposure_callback = None self.initialize() @property def _acquiring(self): return self._camera_acquiring.get_value() @microscope.abc.keep_acquiring def _enable_callback(self, use=False): self.disable() if use: SDK3.RegisterFeatureCallback(self.handle, "ExposureEndEvent", self._exposure_callback, None) self._event_selector.set_string("ExposureEndEvent") self._event_enable.set_value(True) self._using_callback = True else: SDK3.UnregisterFeatureCallback(self.handle, "ExposureEndEvent", self._exposure_callback, None) self._event_enable.set_value(False) self._using_callback = False self.enable() @_acquiring.setter def _acquiring(self, value): # Here to prevent an error when super.__init__ intializes # self._acquiring. Doesn't do anything, because the DLL keeps # track of acquisition state. pass def set_num_buffers(self, num): self.num_buffers = num self._buffers_valid = False def _purge_buffers(self): """Purge buffers on both camera and PC.""" _logger.debug("Purging buffers.") self._buffers_valid = False if self._acquiring: raise microscope.IncompatibleStateError( "Can not modify buffers while camera acquiring.") SDK3.Flush(self.handle) while True: try: self.buffers.get(block=False) except queue.Empty: break def _create_buffers(self, num=None): """Create buffers and store values needed to remove padding later.""" if self._buffers_valid: return if num is None: num = self.num_buffers self._purge_buffers() _logger.debug("Creating %d buffers.", num) self._img_stride = self._aoi_stride.get_value() self._img_width = self._aoi_width.get_value() self._img_height = self._aoi_height.get_value() self._img_encoding = self._pixel_encoding.get_string() img_size = self._image_size_bytes.get_value() self._buffer_size = img_size for i in range(num): buf = np.require( np.empty(img_size), dtype="uint8", requirements=["C_CONTIGUOUS", "ALIGNED", "OWNDATA"], ) self.buffers.put(buf) SDK3.QueueBuffer(self.handle, buf.ctypes.data_as(DPTR_TYPE), img_size) self._buffers_valid = True def invalidate_buffers(self, func): """Wrap functions that invalidate buffers so buffers are recreated.""" outerself = self def wrapper(self, *args, **kwargs): func(self, *args, **kwargs) outerself._buffers_valid = False return wrapper def _fetch_data(self, timeout=5, debug=False): """Fetch data and recycle buffers.""" try: ptr, length = SDK3.WaitBuffer(self.handle, timeout) except SDK3.TimeoutError as e: if debug: _logger.debug(e) return None raw = self.buffers.get() width = self._img_width height = self._img_height data = raw # .reshape((-1, bytes_per_row))[:, 0:width].copy() data = np.empty((height, width), dtype="uint16") SDK3.ConvertBuffer( ptr, data.ctypes.data_as(DPTR_TYPE), width, height, self._img_stride, self._img_encoding, "Mono16", ) # Requeue the buffer if buffer size has not been changed elsewhere. if raw.size == self._buffer_size: self.buffers.put(raw) SDK3.QueueBuffer(self.handle, ptr, length) else: del raw return data def abort(self): """Abort acquisition.""" _logger.debug("Disabling acquisition.") if self._acquiring: self._acquisition_stop() def initialize(self): """Initialise the camera. Open the connection, connect properties and populate settings dict. """ try: self.handle = SDK3.Open(self._index) except Exception as e: raise microscope.InitialiseError("Problem opening camera.") from e if self.handle == None: raise microscope.InitialiseError("No camera opened.") for name, var in sorted(self.__dict__.items()): if isinstance(var, ATProperty): sdk_name = SDK_NAMES[name] if not SDK3.IsImplemented(self.handle, sdk_name): delattr(self, name) continue var.connect(self.handle, sdk_name) if type(var) is ATCommand: continue is_readonly_func = var.is_readonly if type(var) is ATEnum: set_func = var.set_index get_func = var.get_index vals_func = var.get_available_values else: set_func = var.set_value get_func = var.get_value if type(var) is ATString: vals_func = var.max_length elif type(var) in (ATFloat, ATInt): vals_func = lambda v=var: (v.min(), v.max()) else: vals_func = None if name in INVALIDATES_BUFFERS: set_func = self.invalidate_buffers(set_func) self.add_setting( name.lstrip("_"), PROPERTY_TYPES[type(var)], get_func, set_func, vals_func, is_readonly_func, ) # Default setup. self.set_cooling(True) if not self._camera_model.getValue().startswith("SIMCAM"): self._trigger_mode.set_string("Software") self._cycle_mode.set_string("Continuous") else: _logger.warn("No hardware found - using SIMCAM") def callback(*args): data = self._fetch_data(timeout=500) timestamp = time.time() if data is not None: self._put(data, timestamp) return 0 else: return -1 self._exposure_callback = SDK3.CALLBACKTYPE(callback) def set_cooling(self, value): try: self._sensor_cooling.set_value(value) except AttributeError: pass def get_id(self): return self._serial_number.get_value() def _do_shutdown(self) -> None: self.set_cooling(False) SDK3.Close(self.handle) def _do_disable(self): self.abort() self._buffers_valid = False def _do_enable(self): _logger.debug("Preparing for acquisition.") if self._acquiring: self._acquisition_stop() self._create_buffers() self._acquisition_start() _logger.debug("Acquisition enabled: %s.", self._acquiring) return True @microscope.abc.keep_acquiring def set_exposure_time(self, value): bounded_value = sorted( (self._exposure_time.min(), self._exposure_time.max(), value))[1] self._exposure_time.set_value(bounded_value) self._frame_rate.set_value(self._frame_rate.max()) _logger.debug( "Set exposure time to %f, resulting framerate %f.", bounded_value, self._frame_rate.get_value(), ) def get_exposure_time(self): return self._exposure_time.get_value() def get_cycle_time(self): return 1.0 / self._frame_rate.get_value() def _get_sensor_shape(self): return ( self._sensor_width.get_value(), self._sensor_height.get_value(), ) def soft_trigger(self): # deprecated, use triger() return self._software_trigger() @property def trigger_mode(self) -> microscope.TriggerMode: sdk3_string = self._trigger_mode.get_string().lower() return SDK3_STRING_TO_TRIGGER[sdk3_string][1] @property def trigger_type(self) -> microscope.TriggerType: sdk3_string = self._trigger_mode.get_string().lower() return SDK3_STRING_TO_TRIGGER[sdk3_string][0] def set_trigger(self, ttype: microscope.TriggerType, tmode: microscope.TriggerMode) -> None: for available_mode in self._trigger_mode.get_available_values(): trigger = SDK3_STRING_TO_TRIGGER[available_mode.lower()] if trigger == (ttype, tmode): self._trigger_mode.set_string(available_mode) break else: raise microscope.UnsupportedFeatureError( "no SDK3 mode for %s and %s" % (ttype, tmode)) def _do_trigger(self) -> None: self._software_trigger() def get_shuttering_mode(self): return SDK3_STRING_TO_SHUTTERING_MODE[ self._electronic_shuttering_mode.get_string().lower()] def set_shuttering_mode(self, mode): raise NotImplementedError def _get_binning(self): as_text = self._aoi_binning.get_string().split("x") return tuple(int(t) for t in as_text) @microscope.abc.keep_acquiring def _set_binning(self, binning): modes = self._aoi_binning.get_available_values() as_text = "%dx%d" % (binning.h, binning.v) if as_text in modes: self._aoi_binning.set_string(as_text) self._create_buffers() return True else: return False def _get_roi(self): return microscope.ROI( self._aoi_left.get_value(), self._aoi_top.get_value(), self._aoi_width.get_value(), self._aoi_height.get_value(), ) @microscope.abc.keep_acquiring def _set_roi(self, roi): current = self.get_roi() if self._acquiring: self.abort() try: self._aoi_width.set_value(roi.width) self._aoi_height.set_value(roi.height) self._aoi_left.set_value(roi.left) self._aoi_top.set_value(roi.top) except: self._aoi_width.set_value(current.width) self._aoi_height.set_value(current.height) self._aoi_left.set_value(current.left) self._aoi_top.set_value(current.top) return False return True def get_gain(self): if hasattr(self, "_preampgain"): return self._preampgain.get_value() else: return None