Source code for lantz.drivers.andor.ccd

# -*- coding: utf-8 -*-
# pylint: disable=E265

"""
    lantz.drivers.andor.ccd
    ~~~~~~~~~~~~~~~~~~~~~~~

    Low level driver wrapping library for CCD and Intensified CCD cameras.
    Only functions for iXon EMCCD cameras were tested.
    Only tested in Windows OS.

    The driver was written for the single-camera scenario. If more than one
    camera is present, some 'read_once=True' should be erased but it
    shouldn't be necessary to make any more changes.

    Sources::

        - Andor SDK 2.96 Manual

    :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""
import numpy as np
import ctypes as ct
from collections import namedtuple

from lantz import Driver, Feat, Action, DictFeat
from lantz.errors import InstrumentError
from lantz.foreign import LibraryDriver
from lantz import Q_

degC = Q_(1, 'degC')
us = Q_(1, 'us')
MHz = Q_(1, 'MHz')
seg = Q_(1, 's')

_ERRORS = {
    20002: 'DRV_SUCCESS',
    20003: 'DRV_VXDNOTINSTALLED',
    20004: 'DRV_ERROR_SCAN',
    20005: 'DRV_ERROR_CHECK_SUM',
    20006: 'DRV_ERROR_FILELOAD',
    20007: 'DRV_UNKNOWN_FUNCTION',
    20008: 'DRV_ERROR_VXD_INIT',
    20009: 'DRV_ERROR_ADDRESS',
    20010: 'DRV_ERROR_PAGELOCK',
    20011: 'DRV_ERROR_PAGE_UNLOCK',
    20012: 'DRV_ERROR_BOARDTEST',
    20013: 'Unable to communicate with card.',
    20014: 'DRV_ERROR_UP_FIFO',
    20015: 'DRV_ERROR_PATTERN',
    20017: 'DRV_ACQUISITION_ERRORS',
    20018: 'Computer unable to read the data via the ISA slot at the required rate.',
    20019: 'DRV_ACQ_DOWNFIFO_FULL',
    20020: 'RV_PROC_UNKNOWN_INSTRUCTION',
    20021: 'DRV_ILLEGAL_OP_CODE',
    20022: 'Unable to meet Kinetic cycle time.',
    20023: 'Unable to meet Accumulate cycle time.',
    20024: 'No acquisition has taken place',
    20026: 'Overflow of the spool buffer.',
    20027: 'DRV_SPOOLSETUPERROR',
    20033: 'DRV_TEMPERATURE_CODES',
    20034: 'Temperature is OFF.',
    20035: 'Temperature reached but not stabilized.',
    20036: 'Temperature has stabilized at set point.',
    20037: 'Temperature has not reached set point.',
    20038: 'DRV_TEMPERATURE_OUT_RANGE',
    20039: 'DRV_TEMPERATURE_NOT_SUPPORTED',
    20040: 'Temperature had stabilized but has since drifted.',
    20049: 'DRV_GENERAL_ERRORS',
    20050: 'DRV_INVALID_AUX',
    20051: 'DRV_COF_NOTLOADED',
    20052: 'DRV_FPGAPROG',
    20053: 'DRV_FLEXERROR',
    20054: 'DRV_GPIBERROR',
    20064: 'DRV_DATATYPE',
    20065: 'DRV_DRIVER_ERRORS',
    20066: 'Invalid parameter 1',
    20067: 'Invalid parameter 2',
    20068: 'Invalid parameter 3',
    20069: 'Invalid parameter 4',
    20070: 'DRV_INIERROR',
    20071: 'DRV_COFERROR',
    20072: 'Acquisition in progress',
    20073: 'The system is not currently acquiring',
    20074: 'DRV_TEMPCYCLE',
    20075: 'System not initialized',
    20076: 'DRV_P5INVALID',
    20077: 'DRV_P6INVALID',
    20078: 'Not a valid mode',
    20079: 'DRV_INVALID_FILTER',
    20080: 'DRV_I2CERRORS',
    20081: 'DRV_DRV_I2CDEVNOTFOUND',
    20082: 'DRV_I2CTIMEOUT',
    20083: 'DRV_P7INVALID',
    20089: 'DRV_USBERROR',
    20090: 'DRV_IOCERROR',
    20091: 'DRV_VRMVERSIONERROR',
    20093: 'DRV_USB_INTERRUPT_ENDPOINT_ERROR',
    20094: 'DRV_RANDOM_TRACK_ERROR',
    20095: 'DRV_INVALID_TRIGGER_MODE',
    20096: 'DRV_LOAD_FIRMWARE_ERROR',
    20097: 'DRV_DIVIDE_BY_ZERO_ERROR',
    20098: 'DRV_INVALID_RINGEXPOSURES',
    20099: 'DRV_BINNING_ERROR',
    20990: 'No camera present',
    20991: 'Feature not supported on this camera.',
    20992: 'Feature is not available at the moment.',
    20115: 'DRV_ERROR_MAP',
    20116: 'DRV_ERROR_UNMAP',
    20117: 'DRV_ERROR_MDL',
    20118: 'DRV_ERROR_UNMDL',
    20119: 'DRV_ERROR_BUFFSIZE',
    20121: 'DRV_ERROR_NOHANDLE',
    20130: 'DRV_GATING_NOT_AVAILABLE',
    20131: 'DRV_FPGA_VOLTAGE_ERROR',
    20100: 'DRV_INVALID_AMPLIFIER',
    20101: 'DRV_INVALID_COUNTCONVERT_MODE'
}


[docs]class CCD(LibraryDriver): LIBRARY_NAME = 'atmcd64d.dll' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.cameraIndex = ct.c_int(0) def _patch_functions(self): internal = self.lib.internal internal.GetCameraSerialNumber.argtypes = [ct.pointer(ct.c_uint)] internal.Filter_SetAveragingFactor.argtypes = [ct.c_int] internal.Filter_SetThreshold.argtypes = ct.c_float internal.Filter_GetThreshold.argtypes = ct.c_float def _return_handler(self, func_name, ret_value): excl_func = ['GetTemperatureF', 'IsCountConvertModeAvailable', 'IsAmplifierAvailable', 'IsTriggerModeAvailable'] if ret_value != 20002 and func_name not in excl_func: raise InstrumentError('{}'.format(_ERRORS[ret_value])) return ret_value
[docs] def initialize(self): """ This function will initialize the Andor SDK System. As part of the initialization procedure on some cameras (i.e. Classic, iStar and earlier iXion) the DLL will need access to a DETECTOR.INI which contains information relating to the detector head, number pixels, readout speeds etc. If your system has multiple cameras then see the section Controlling multiple cameras. """ self.lib.Initialize() self.triggers = {'Internal': 0, 'External': 1, 'External Start': 6, 'External Exposure': 7, 'External FVB EM': 9, 'Software Trigger': 10, 'External Charge Shifting': 12} self.savetypes = {'Signed16bits': 1, 'Signed32bits': 2, 'Float': 3} # Initial values self.readout_packing_state = False self.readout_packing = self.readout_packing_state self.readout_mode_mode = 'Image' self.readout_mode = self.readout_mode_mode self.photon_counting_mode_state = False self.photon_counting_mode = self.photon_counting_mode_state self.frame_transfer_mode_state = False self.frame_transfer_mode = self.frame_transfer_mode_state self.fan_mode_index = 'onfull' self.fan_mode = self.fan_mode_index self.EM_gain_mode_index = 'RealGain' self.EM_gain_mode = self.EM_gain_mode_index self.cooled_on_shutdown_value = False self.cooled_on_shutdown = self.cooled_on_shutdown_value self.baseline_offset_value = 100 self.baseline_offset = self.baseline_offset_value self.adv_trigger_mode_state = True self.adv_trigger_mode = self.adv_trigger_mode_state self.acq_mode = 'Single Scan' self.acquisition_mode = self.acq_mode self.amp_typ = 0 self.horiz_shift_speed_index = 0 self.horiz_shift_speed = self.horiz_shift_speed_index self.vert_shift_speed_index = 0 self.vert_shift_speed = self.vert_shift_speed_index self.preamp_index = 0 self.preamp = self.preamp_index self.temperature_sp = 0 * degC self.temperature_setpoint = self.temperature_sp self.auxout = np.zeros(4, dtype=bool) for i in np.arange(1, 5): self.out_aux_port[i] = False self.trigger_mode_index = 'Internal' self.trigger_mode = self.trigger_mode_index
[docs] def finalize(self): """Finalize Library. Concluding function. """ if self.status != 'Camera is idle, waiting for instructions.': self.abort_acquisition() self.cooler_on = False self.free_int_mem() self.lib.ShutDown()
### SYSTEM INFORMATION @Feat(read_once=True) def ncameras(self): """This function returns the total number of Andor cameras currently installed. It is possible to call this function before any of the cameras are initialized. """ n = ct.c_long() self.lib.GetAvailableCameras(ct.pointer(n)) return n.value
[docs] def camera_handle(self, index): """This function returns the handle for the camera specified by cameraIndex. When multiple Andor cameras are installed the handle of each camera must be retrieved in order to select a camera using the SetCurrentCamera function. The number of cameras can be obtained using the GetAvailableCameras function. :param index: index of any of the installed cameras. Valid values: 0 to NumberCameras-1 where NumberCameras is the value returned by the GetAvailableCameras function. """ index = ct.c_long(index) handle = ct.c_long() self.lib.GetCameraHandle(index, ct.pointer(handle)) return handle.value
@Feat() def current_camera(self): """When multiple Andor cameras are installed this function allows the user to select which camera is currently active. Once a camera has been selected the other functions can be called as normal but they will only apply to the selected camera. If only 1 camera is installed calling this function is not required since that camera will be selected by default. """ n = ct.c_long() # current camera handler self.lib.GetCurrentCamera(ct.pointer(n)) return n.value @current_camera.setter def current_camera(self, value): value = ct.c_long(value) self.lib.SetCurrentCamera(value.value) # needs camera handler @Feat(read_once=True) def idn(self): """Identification of the device """ hname = (ct.c_char * 100)() self.lib.GetHeadModel(ct.pointer(hname)) hname = str(hname.value)[2:-1] sn = ct.c_uint() self.lib.GetCameraSerialNumber(ct.pointer(sn)) return 'Andor ' + hname + ', serial number ' + str(sn.value) @Feat(read_once=True) def hardware_version(self): pcb, decode = ct.c_uint(), ct.c_uint() dummy1, dummy2 = ct.c_uint(), ct.c_uint() firmware_ver, firmware_build = ct.c_uint(), ct.c_uint() self.lib.GetHardwareVersion(ct.pointer(pcb), ct.pointer(decode), ct.pointer(dummy1), ct.pointer(dummy2), ct.pointer(firmware_ver), ct.pointer(firmware_build)) results = namedtuple('hardware_versions', 'PCB Flex10K CameraFirmware CameraFirmwareBuild') return results(pcb.value, decode.value, firmware_ver.value, firmware_build.value) @Feat(read_once=True) def software_version(self): eprom, coffile, vxdrev = ct.c_uint(), ct.c_uint(), ct.c_uint() vxdver, dllrev, dllver = ct.c_uint(), ct.c_uint(), ct.c_uint() self.lib.GetSoftwareVersion(ct.pointer(eprom), ct.pointer(coffile), ct.pointer(vxdrev), ct.pointer(vxdver), ct.pointer(dllrev), ct.pointer(dllver)) results = namedtuple('software_versions', 'EPROM COF DriverRev DriverVer DLLRev DLLVer') return results(eprom.value, coffile.value, vxdrev.value, vxdver.value, dllrev.value, dllver.value) # TODO: Make sense of this: @Feat(read_once=True) def capabilities(self): """This function will fill in an AndorCapabilities structure with the capabilities associated with the connected camera. Individual capabilities are determined by examining certain bits and combinations of bits in the member variables of the AndorCapabilites structure. """ class Capabilities(ct.Structure): _fields_ = [("Size", ct.c_ulong), ("AcqModes", ct.c_ulong), ("ReadModes", ct.c_ulong), ("FTReadModes", ct.c_ulong), ("TriggerModes", ct.c_ulong), ("CameraType", ct.c_ulong), ("PixelModes", ct.c_ulong), ("SetFunctions", ct.c_ulong), ("GetFunctions", ct.c_ulong), ("Features", ct.c_ulong), ("PCICard", ct.c_ulong), ("EMGainCapability", ct.c_ulong)] stru = Capabilities() stru.Size = ct.sizeof(stru) self.lib.GetCapabilities(ct.pointer(stru)) return stru @Feat(read_once=True) def controller_card(self): """This function will retrieve the type of PCI controller card included in your system. This function is not applicable for USB systems. The maximum number of characters that can be returned from this function is 10. """ model = ct.c_wchar_p() self.lib.GetControllerCardModel(ct.pointer(model)) return model.value @Feat(read_once=True) def count_convert_wavelength_range(self): """This function returns the valid wavelength range available in Count Convert mode.""" mini = ct.c_float() maxi = ct.c_float() self.lib.GetCountConvertWavelengthRange(ct.pointer(mini), ct.pointer(maxi)) return (mini.value, maxi.value) @Feat(read_once=True) def detector_shape(self): xp, yp = ct.c_int(), ct.c_int() self.lib.GetDetector(ct.pointer(xp), ct.pointer(yp)) return (xp.value, yp.value) @Feat(read_once=True) def px_size(self): """This function returns the dimension of the pixels in the detector in microns. """ xp, yp = ct.c_float(), ct.c_float() self.lib.GetPixelSize(ct.pointer(xp), ct.pointer(yp)) return (xp.value, yp.value)
[docs] def QE(self, wl): """Returns the percentage QE for a particular head model at a user specified wavelength. """ hname = (ct.c_char * 100)() self.lib.GetHeadModel(ct.pointer(hname)) wl = ct.c_float(wl) qe = ct.c_float() self.lib.GetQE(ct.pointer(hname), wl, ct.c_uint(0), ct.pointer(qe)) return qe.value
[docs] def sensitivity(self, ad, amp, i, pa): """This function returns the sensitivity for a particular speed. """ sens = ct.c_float() ad, amp, i, pa = ct.c_int(ad), ct.c_int(amp), ct.c_int(i), ct.c_int(pa) self.lib.GetSensitivity(ad, amp, i, pa, ct.pointer(sens)) return sens.value
[docs] def count_convert_available(self, mode): """This function checks if the hardware and current settings permit the use of the specified Count Convert mode. """ mode = ct.c_int(mode) ans = self.lib.IsCountConvertModeAvailable(mode) if ans == 20002: return True else: return False
### SHUTTER # I couldn't find a better way to do this... sorry @Action() def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode): """This function expands the control offered by SetShutter to allow an external shutter and internal shutter to be controlled independently (only available on some cameras – please consult your Camera User Guide). The typ parameter allows the user to control the TTL signal output to an external shutter. The opening and closing times specify the length of time required to open and close the shutter (this information is required for calculating acquisition timings – see SHUTTER TRANSFER TIME). The mode and extmode parameters control the behaviour of the internal and external shutters. To have an external shutter open and close automatically in an experiment, set the mode parameter to “Open” and set the extmode parameter to “Auto”. To have an internal shutter open and close automatically in an experiment, set the extmode parameter to “Open” and set the mode parameter to “Auto”. To not use any shutter in the experiment, set both shutter modes to permanently open. :param typ: 0 (or 1) Output TTL low (or high) signal to open shutter. :param mode: Internal shutter: 0 Fully Auto, 1 Permanently Open, 2 Permanently Closed, 4 Open for FVB series, 5 Open for any series. :param ext_closing: Time shutter takes to close (milliseconds) :param ext_opening: Time shutter takes to open (milliseconds) :param ext_mode: External shutter: 0 Fully Auto, 1 Permanently Open, 2 Permanently Closed, 4 Open for FVB series, 5 Open for any series. """ self.lib.SetShutterEx(ct.c_int(typ), ct.c_int(mode), ct.c_int(ext_closing), ct.c_int(ext_opening), ct.c_int(ext_mode)) @Feat(read_once=True) def shutter_min_times(self): """ This function will return the minimum opening and closing times in milliseconds for the shutter on the current camera. """ otime, ctime = ct.c_int(), ct.c_int() self.lib.GetShutterMinTimes(ct.pointer(ctime), ct.pointer(otime)) return (otime.value, ctime.value) @Feat(read_once=True) def has_mechanical_shutter(self): state = ct.c_int() self.lib.IsInternalMechanicalShutter(ct.pointer(state)) return bool(state.value) ### TEMPERATURE @Feat(read_once=True, units='degC') def min_temperature(self): """This function returns the valid range of temperatures in centigrads to which the detector can be cooled. """ mini, maxi = ct.c_int(), ct.c_int() self.lib.GetTemperatureRange(ct.pointer(mini), ct.pointer(maxi)) return mini.value @Feat(read_once=True, units='degC') def max_temperature(self): """This function returns the valid range of temperatures in centigrads to which the detector can be cooled. """ mini, maxi = ct.c_int(), ct.c_int() self.lib.GetTemperatureRange(ct.pointer(mini), ct.pointer(maxi)) return maxi.value @Feat() def temperature_status(self): """This function returns the temperature of the detector to the nearest degree. It also gives the status of cooling process. """ temp = ct.c_float() ans = self.lib.GetTemperatureF(ct.pointer(temp)) return _ERRORS[ans] @Feat(units='degC') def temperature(self): """This function returns the temperature of the detector to the nearest degree. It also gives the status of cooling process. """ temp = ct.c_float() self.lib.GetTemperatureF(ct.pointer(temp)) return temp.value @Feat(units='degC') def temperature_setpoint(self): return self.temperature_sp @temperature_setpoint.setter def temperature_setpoint(self, value): self.temperature_sp = value value = ct.c_int(int(value)) self.lib.SetTemperature(value) @Feat(values={True: 1, False: 0}) def cooler_on(self): state = ct.c_int() self.lib.IsCoolerOn(ct.pointer(state)) return state.value @cooler_on.setter def cooler_on(self, value): if value: self.lib.CoolerON() else: self.lib.CoolerOFF() @Feat(values={True: 1, False: 0}) def cooled_on_shutdown(self): """This function determines whether the cooler is switched off when the camera is shut down. """ return self.cooled_on_shutdown_value @cooled_on_shutdown.setter def cooled_on_shutdown(self, state): ans = self.lib.SetCoolerMode(ct.c_int(state)) if ans == 20002: self.cooled_on_shutdown_value = state @Feat(values={'onfull': 0, 'onlow': 1, 'off': 2}) def fan_mode(self): """Allows the user to control the mode of the camera fan. If the system is cooled, the fan should only be turned off for short periods of time. During this time the body of the camera will warm up which could compromise cooling capabilities. If the camera body reaches too high a temperature, depends on camera, the buzzer will sound. If this happens, turn off the external power supply and allow the system to stabilize before continuing. """ return self.fan_mode_index @fan_mode.setter def fan_mode(self, mode): ans = self.lib.SetFanMode(ct.c_int(mode)) if ans == 20002: self.fan_mode_index = mode ### FILTERS @Feat() def averaging_factor(self): """Averaging factor to be used with the recursive filter. For information on the various data averaging filters available see DATA AVERAGING FILTERS in the Special Guides section of the manual. """ af = ct.c_uint() self.lib.Filter_GetAveragingFactor(ct.pointer(af)) return af.value @averaging_factor.setter def averaging_factor(self, value): self.lib.Filter_SetAveragingFactor(ct.c_uint(value)) @Feat() def averaging_frame_count(self): """Number of frames to be used when using the frame averaging filter. """ fc = ct.c_uint() self.lib.Filter_GetAveragingFrameCount(ct.pointer(fc)) return fc.value @averaging_frame_count.setter def averaging_frame_count(self, value): self.lib.Filter_SetAveragingFrameCount(ct.c_uint(value)) @Feat(values={'NAF': 0, 'RAF': 5, 'FAF': 6}) def averaging_mode(self): """Current averaging mode. Valid options are: 0 – No Averaging Filter 5 – Recursive Averaging Filter 6 – Frame Averaging Filter """ i = ct.c_int() self.lib.Filter_GetDataAveragingMode(ct.pointer(i)) return i.value @averaging_mode.setter def averaging_mode(self, value): self.lib.Filter_SetDataAveragingMode(ct.c_int(value)) @Feat(values={'NF': 0, 'MF': 1, 'LAF': 2, 'IRF': 3, 'NTF': 4}) def noise_filter_mode(self): """Set the Noise Filter to use; For information on the various spurious noise filters available see SPURIOUS NOISE FILTERS in the Special Guides section of the manual. Valid options are: 0 – No Averaging Filter 1 – Median Filter 2 – Level Above Filter 3 – Interquartile Range Filter 4 – Noise Threshold Filter """ i = ct.c_uint() self.lib.Filter_GetMode(ct.pointer(i)) return i.value @noise_filter_mode.setter def noise_filter_mode(self, value): self.lib.Filter_SetMode(ct.c_uint(value)) @Feat() def filter_threshold(self): """Sets the threshold value for the Noise Filter. For information on the various spurious noise filters available see SPURIOUS NOISE FILTERS in the Special Guides section of the manual. Valid values are: 0 – 65535 for Level Above filte 0 – 10 for all other filters. """ f = ct.c_float() self.lib.Filter_GetThreshold(ct.pointer(f)) return f.value @filter_threshold.setter def filter_threshold(self, value): self.lib.Filter_SetThreshold(ct.c_float(value)) @Feat(values={True: 2, False: 0}) def cr_filter_enabled(self): """This function will set the state of the cosmic ray filter mode for future acquisitions. If the filter mode is on, consecutive scans in an accumulation will be compared and any cosmic ray-like features that are only present in one scan will be replaced with a scaled version of the corresponding pixel value in the correct scan. """ i = ct.c_int() self.lib.GetFilterMode(ct.pointer(i)) return i.value @cr_filter_enabled.setter def cr_filter_enabled(self, value): self.lib.SetFilterMode(ct.c_int(value)) ### PHOTON COUNTING MODE @Feat(values={True: 1, False: 0}) # FIXME: untested def photon_counting_mode(self): """This function activates the photon counting option. """ return self.photon_counting_mode_state @photon_counting_mode.setter def photon_counting_mode(self, state): ans = self.lib.SetPhotonCounting(ct.c_int(state)) if ans == 20002: self.photon_counting_mode_state = state @Feat(read_once=True) def n_photon_counting_div(self): """Available in some systems is photon counting mode. This function gets the number of photon counting divisions available. The functions SetPhotonCounting and SetPhotonCountingThreshold can be used to specify which of these divisions is to be used. """ inti = ct.c_ulong() self.lib.GetNumberPhotonCountingDivisions(ct.pointer(inti)) return inti.value @Action() # untested def set_photon_counting_divs(self, n, thres): """This function sets the thresholds for the photon counting option. """ thres = ct.c_long(thres) self.lib.SetPhotonCountingDivisions(ct.c_ulong(n), ct.pointer(thres)) @Action() def set_photon_counting_thres(self, mini, maxi): """This function sets the minimum and maximum threshold in counts (1-65535) for the photon counting option. """ self.lib.SetPhotonCountingThreshold(ct.c_long(mini), ct.c_long(maxi)) ### FAST KINETICS MODE @Feat(units='s') def FK_exposure_time(self): """This function will return the current “valid” exposure time for a fast kinetics acquisition. This function should be used after all the acquisitions settings have been set, i.e. SetFastKinetics and SetFKVShiftSpeed. The value returned is the actual time used in subsequent acquisitions. """ f = ct.c_float() self.lib.GetFKExposureTime(ct.pointer(f)) return f.value ### ACQUISITION HANDLING @Feat(values={'Single Scan': 1, 'Accumulate': 2, 'Kinetics': 3, 'Fast Kinetics': 4, 'Run till abort': 5}) def acquisition_mode(self): """This function will set the acquisition mode to be used on the next StartAcquisition. NOTE: In Mode 5 the system uses a “Run Till Abort” acquisition mode. In Mode 5 only, the camera continually acquires data until the AbortAcquisition function is called. By using the SetDriverEvent function you will be notified as each acquisition is completed. """ return self.acq_mode @acquisition_mode.setter def acquisition_mode(self, mode): ans = self.lib.SetAcquisitionMode(ct.c_int(mode)) if ans == 20002: self.acq_mode = mode @Action() def prepare_acquisition(self): """This function reads the current acquisition setup and allocates and configures any memory that will be used during the acquisition. The function call is not required as it will be called automatically by the StartAcquisition function if it has not already been called externally. However for long kinetic series acquisitions the time to allocate and configure any memory can be quite long which can result in a long delay between calling StartAcquisition and the acquisition actually commencing. For iDus, there is an additional delay caused by the camera being set-up with any new acquisition parameters. Calling PrepareAcquisition first will reduce this delay in the StartAcquisition call. """ self.lib.PrepareAcquisition() @Action() def start_acquisition(self): """This function starts an acquisition. The status of the acquisition can be monitored via GetStatus(). """ self.lib.StartAcquisition() @Action() def abort_acquisition(self): """This function aborts the current acquisition if one is active """ self.lib.AbortAcquisition() @Action() def wait_for_acquisition(self): """WaitForAcquisition can be called after an acquisition is started using StartAcquisition to put the calling thread to sleep until an Acquisition Event occurs. This can be used as a simple alternative to the functionality provided by the SetDriverEvent function, as all Event creation and handling is performed internally by the SDK library. Like the SetDriverEvent functionality it will use less processor resources than continuously polling with the GetStatus function. If you wish to restart the calling thread without waiting for an Acquisition event, call the function CancelWait. An Acquisition Event occurs each time a new image is acquired during an Accumulation, Kinetic Series or Run-Till-Abort acquisition or at the end of a Single Scan Acquisition. If a second event occurs before the first one has been acknowledged, the first one will be ignored. Care should be taken in this case, as you may have to use CancelWait to exit the function. """ self.lib.WaitForAcquisition() @Action() def cancel_wait(self): """This function restarts a thread which is sleeping within the WaitForAcquisition function. The sleeping thread will return from WaitForAcquisition with a value not equal to DRV_SUCCESS. """ self.lib.CancelWait() @Feat() def acquisition_progress(self): """This function will return information on the progress of the current acquisition. It can be called at any time but is best used in conjunction with SetDriverEvent. The values returned show the number of completed scans in the current acquisition. If 0 is returned for both accum and series then either: - No acquisition is currently running - The acquisition has just completed - The very first scan of an acquisition has just started and not yet completed. GetStatus can be used to confirm if the first scan has just started, returning DRV_ACQUIRING, otherwise it will return DRV_IDLE. For example, if accum=2 and series=3 then the acquisition has completed 3 in the series and 2 accumulations in the 4 scan of the series """ acc = ct.c_long() series = ct.c_long() self.lib.GetAcquisitionProgress(ct.pointer(acc), ct.pointer(series)) return acc.value, series.value @Feat() def status(self): """This function will return the current status of the Andor SDK system. This function should be called before an acquisition is started to ensure that it is IDLE and during an acquisition to monitor the process. """ st = ct.c_int() self.lib.GetStatus(ct.pointer(st)) if st.value == 20073: return 'Camera is idle, waiting for instructions.' elif st.value == 20074: return 'Camera is executing the temperature cycle.' elif st.value == 20072: return 'Acquisition in progress.' elif st.value == 20023: return 'Unable to meet accumulate cycle time.' elif st.value == 20022: return 'Unable to meet kinetic cycle time.' elif st.value == 20013: return 'Unable to communicate with card.' elif st.value == 20018: return ('Computer unable to read the data via the ISA slot at the ' 'required rate.') elif st.value == 20026: return 'Overflow of the spool buffer.' @Feat() def n_exposures_in_ring(self): """Gets the number of exposures in the ring at this moment.""" n = ct.c_int() self.lib.GetNumberRingExposureTimes(ct.pointer(n)) return n.value @Feat() def buffer_size(self): """This function will return the maximum number of images the circular buffer can store based on the current acquisition settings. """ n = ct.c_long() self.lib.GetSizeOfCircularBuffer(ct.pointer(n)) return n.value @Feat(values={True: 1, False: 0}) def exposing(self): """This function will return if the system is exposing or not. The status of the firepulse will be returned. NOTE This is only supported by the CCI23 card. """ i = ct.c_int() self.lib.GetCameraEventStatus(ct.pointer(i)) return i.value @Feat() def n_images_acquired(self): """This function will return the total number of images acquired since the current acquisition started. If the camera is idle the value returned is the number of images acquired during the last acquisition. """ n = ct.c_long() self.lib.GetTotalNumberImagesAcquired(ct.pointer(n)) return n.value @Action() def set_image(self, shape=None, binned=(1, 1), p_0=(1, 1)): """This function will set the horizontal and vertical binning to be used when taking a full resolution image. :param hbin: number of pixels to bin horizontally. :param vbin: number of pixels to bin vertically. :param hstart: Start column (inclusive). :param hend: End column (inclusive). :param vstart: Start row (inclusive). :param vend: End row (inclusive). """ if shape is None: shape = self.detector_shape (hbin, vbin) = binned (hstart, vstart) = p_0 (hend, vend) = (p_0[0] + shape[0] - 1, p_0[1] + shape[1] - 1) self.lib.SetImage(ct.c_int(hbin), ct.c_int(vbin), ct.c_int(hstart), ct.c_int(hend), ct.c_int(vstart), ct.c_int(vend)) @Feat(values={'FVB': 0, 'Multi-Track': 1, 'Random-Track': 2, 'Single-Track': 3, 'Image': 4}) def readout_mode(self): """This function will set the readout mode to be used on the subsequent acquisitions. """ return self.readout_mode_mode @readout_mode.setter def readout_mode(self, mode): ans = self.lib.SetReadMode(ct.c_int(mode)) if ans == 20002: self.readout_mode_mode = mode @Feat(values={True: 1, False: 0}) def readout_packing(self): """This function will configure whether data is packed into the readout register to improve frame rates for sub-images. Note: It is important to ensure that no light falls outside of the sub-image area otherwise the acquired data will be corrupted. Only currently available on iXon+ and iXon3. """ return self.readout_packing_state @readout_packing.setter def readout_packing(self, state): ans = self.lib.SetReadoutRegisterPacking(ct.c_int(state)) if ans == 20002: self.readout_packing_state = state ### DATA HANDLING @Feat(read_once=True) def min_image_length(self): """This function will return the minimum number of pixels that can be read out from the chip at each exposure. This minimum value arises due the way in which the chip is read out and will limit the possible sub image dimensions and binning sizes that can be applied. """ # Will contain the minimum number of super pixels on return. px = ct.c_int() self.lib.GetMinimumImageLength(ct.pointer(px)) return px.value @Action() def free_int_mem(self): """The FreeInternalMemory function will deallocate any memory used internally to store the previously acquired data. Note that once this function has been called, data from last acquisition cannot be retrived. """ self.lib.FreeInternalMemory()
[docs] def acquired_data(self, shape): """This function will return the data from the last acquisition. The data are returned as long integers (32-bit signed integers). The “array” must be large enough to hold the complete data set. """ size = np.array(shape).prod() arr = np.ascontiguousarray(np.zeros(size, dtype=np.int32)) self.lib.GetAcquiredData(arr.ctypes.data_as(ct.POINTER(ct.c_int32)), ct.c_ulong(size)) arr = arr.reshape(shape) return arr
[docs] def acquired_data16(self, shape): """16-bit version of the GetAcquiredData function. The “array” must be large enough to hold the complete data set. """ size = np.array(shape).prod() arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) self.lib.GetAcquiredData16(arr.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size)) return arr.reshape(shape)
[docs] def oldest_image(self, shape): """This function will update the data array with the oldest image in the circular buffer. Once the oldest image has been retrieved it no longer is available. The data are returned as long integers (32-bit signed integers). The "array" must be exactly the same size as the full image. """ size = np.array(shape).prod() array = np.ascontiguousarray(np.zeros(size, dtype=np.int32)) self.lib.GetOldestImage(array.ctypes.data_as(ct.POINTER(ct.c_int32)), ct.c_ulong(size)) return array.reshape(shape)
[docs] def oldest_image16(self, shape): """16-bit version of the GetOldestImage function. """ size = np.array(shape).prod() array = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) self.lib.GetOldestImage16(array.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size)) return array.reshape(shape)
[docs] def most_recent_image(self, shape): """This function will update the data array with the most recently acquired image in any acquisition mode. The data are returned as long integers (32-bit signed integers). The "array" must be exactly the same size as the complete image. """ size = np.array(shape).prod() arr = np.ascontiguousarray(np.zeros(size, dtype=np.int32)) self.lib.GetMostRecentImage(arr.ctypes.data_as(ct.POINTER(ct.c_int32)), ct.c_ulong(size)) return arr.reshape(shape)
[docs] def most_recent_image16(self, shape): """16-bit version of the GetMostRecentImage function. """ size = np.array(shape).prod() arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) pt = ct.POINTER(ct.c_int16) self.lib.GetMostRecentImage16(arr.ctypes.data_as(pt), ct.c_ulong(size)) return arr.reshape(shape)
[docs] def images(self, first, last, shape, validfirst, validlast): """This function will update the data array with the specified series of images from the circular buffer. If the specified series is out of range (i.e. the images have been overwritten or have not yet been acquired) then an error will be returned. :param first: index of first image in buffer to retrieve. :param flast: index of last image in buffer to retrieve. :param farr: pointer to data storage allocated by the user. :param size: total number of pixels. :param fvalidfirst: index of the first valid image. :param fvalidlast: index of the last valid image. """ size = shape[0] * shape[1] * (1 + last - first) array = np.ascontiguousarray(np.zeros(size, dtype=np.int32)) self.lib.GetImages(ct.c_long(first), ct.c_long(last), array.ctypes.data_as(ct.POINTER(ct.c_int32)), ct.c_ulong(size), ct.pointer(ct.c_long(validfirst)), ct.pointer(ct.c_long(validlast))) return array.reshape(-1, shape[0], shape[1])
[docs] def images16(self, first, last, shape, validfirst, validlast): """16-bit version of the GetImages function. """ size = shape[0] * shape[1] * (1 + last - first) array = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) self.lib.GetImages16(ct.c_long(first), ct.c_long(last), array.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size), ct.pointer(ct.c_long(validfirst)), ct.pointer(ct.c_long(validlast))) return array.reshape(-1, shape[0], shape[1])
@Feat() def new_images_index(self): """This function will return information on the number of new images (i.e. images which have not yet been retrieved) in the circular buffer. This information can be used with GetImages to retrieve a series of the latest images. If any images are overwritten in the circular buffer they can no longer be retrieved and the information returned will treat overwritten images as having been retrieved. """ first = ct.c_long() last = ct.c_long() self.lib.GetNumberNewImages(ct.pointer(first), ct.pointer(last)) return (first.value, last.value) @Feat() # TODO: test this def available_images_index(self): """This function will return information on the number of available images in the circular buffer. This information can be used with GetImages to retrieve a series of images. If any images are overwritten in the circular buffer they no longer can be retrieved and the information returned will treat overwritten images as not available. """ first = ct.c_long() last = ct.c_long() self.lib.GetNumberAvailableImages(ct.pointer(first), ct.pointer(last)) return (first.value, last.value)
[docs] def set_dma_parameters(self, n_max_images, s_per_dma): """In order to facilitate high image readout rates the controller card may wait for multiple images to be acquired before notifying the SDK that new data is available. Without this facility, there is a chance that hardware interrupts may be lost as the operating system does not have enough time to respond to each interrupt. The drawback to this is that you will not get the data for an image until all images for that interrupt have been acquired. There are 3 settings involved in determining how many images will be acquired for each notification (DMA Interrupt) of the controller card and they are as follows: 1. The size of the DMA buffer gives an upper limit on the number of images that can be stored within it and is usually set to the size of one full image when installing the software. This will usually mean that if you acquire full frames there will never be more than one image per DMA. 2. A second setting that is used is the minimum amount of time (SecondsPerDMA) that should expire between interrupts. This can be used to give an indication of the reponsiveness of the operating system to interrupts. Decreasing this value will allow more interrupts per second and should only be done for faster pcs. The default value is 0.03s (30ms), finding the optimal value for your pc can only be done through experimentation. 3. The third setting is an overide to the number of images calculated using the previous settings. If the number of images per dma is calculated to be greater than MaxImagesPerDMA then it will be reduced to MaxImagesPerDMA. This can be used to, for example, ensure that there is never more than 1 image per DMA by setting MaxImagesPerDMA to 1. Setting MaxImagesPerDMA to zero removes this limit. Care should be taken when modifying these parameters as missed interrupts may prevent the acquisition from completing. """ self.lib.SetDMAParameters(ct.c_int(n_max_images), ct.c_float(s_per_dma))
@Feat() def max_images_per_dma(self): """This function will return the maximum number of images that can be transferred during a single DMA transaction. """ n = ct.c_ulong() self.lib.GetImagesPerDMA(ct.pointer(n)) return n.value @Action() def save_raw(self, filename, typ): """This function saves the last acquisition as a raw data file. See self.savetypes for the file type keys. """ self.lib.SaveAsRaw(ct.c_char_p(str.encode(filename)), ct.c_int(self.savetypes[typ])) ### EXPOSURE SETTINGS @Feat() def acquisition_timings(self): """This function will return the current “valid” acquisition timing information. This function should be used after all the acquisitions settings have been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode etc. The values returned are the actual times used in subsequent acquisitions. This function is required as it is possible to set the exposure time to 20ms, accumulate cycle time to 30ms and then set the readout mode to full image. As it can take 250ms to read out an image it is not possible to have a cycle time of 30ms. All data is measured in seconds. """ exp = ct.c_float() accum = ct.c_float() kine = ct.c_float() self.lib.GetAcquisitionTimings(ct.pointer(exp), ct.pointer(accum), ct.pointer(kine)) return exp.value * seg, accum.value * seg, kine.value * seg @Action() def set_exposure_time(self, time): """This function will set the exposure time to the nearest valid value not less than the given value, in seconds. The actual exposure time used is obtained by GetAcquisitionTimings. Please refer to SECTION 5 – ACQUISITION MODES for further information. """ try: time.magnitude except AttributeError: time = time * seg self.lib.SetExposureTime(ct.c_float(time.magnitude)) @Action() def set_accum_time(self, time): """This function will set the accumulation cycle time to the nearest valid value not less than the given value. The actual cycle time used is obtained by GetAcquisitionTimings. Please refer to SECTION 5 – ACQUISITION MODES for further information. """ try: time.magnitude except AttributeError: time = time * seg self.lib.SetAccumulationCycleTime(ct.c_float(time.magnitude)) @Action() def set_kinetic_cycle_time(self, time): """This function will set the kinetic cycle time to the nearest valid value not less than the given value. The actual time used is obtained by GetAcquisitionTimings. . Please refer to SECTION 5 – ACQUISITION MODES for further information. float time: the kinetic cycle time in seconds. """ try: time.magnitude except AttributeError: time = time * seg self.lib.SetKineticCycleTime(ct.c_float(time.magnitude)) @Action() def set_n_kinetics(self, n): """This function will set the number of scans (possibly accumulated scans) to be taken during a single acquisition sequence. This will only take effect if the acquisition mode is Kinetic Series. """ self.lib.SetNumberKinetics(ct.c_int(n)) @Action() def set_n_accum(self, n): """This function will set the number of scans accumulated in memory. This will only take effect if the acquisition mode is either Accumulate or Kinetic Series. """ self.lib.SetNumberAccumulations(ct.c_int(n)) @Feat(units='s') def keep_clean_time(self): """This function will return the time to perform a keep clean cycle. This function should be used after all the acquisitions settings have been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode etc. The value returned is the actual times used in subsequent acquisitions. """ time = ct.c_float() self.lib.GetKeepCleanTime(ct.pointer(time)) return time.value @Feat(units='s') def readout_time(self): """This function will return the time to readout data from a sensor. This function should be used after all the acquisitions settings have been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode etc. The value returned is the actual times used in subsequent acquisitions. """ time = ct.c_float() self.lib.GetReadOutTime(ct.pointer(time)) return time.value @Feat(read_once=True, units='s') def max_exposure(self): """This function will return the maximum Exposure Time in seconds that is settable by the SetExposureTime function. """ exp = ct.c_float() self.lib.GetMaximumExposure(ct.pointer(exp)) return exp.value @Feat(read_once=True) def n_max_nexposure(self): """This function will return the maximum number of exposures that can be configured in the SetRingExposureTimes SDK function. """ n = ct.c_int() self.lib.GetMaximumNumberRingExposureTimes(ct.pointer(n)) return n.value
[docs] def true_exposure_times(self, n): # FIXME: bit order? something """This function will return the actual exposure times that the camera will use. There may be differences between requested exposures and the actual exposures. ntimes: Numbers of times requested. """ times = np.ascontiguousarray(np.zeros(n, dtype=np.float)) outtimes = times.ctypes.data_as(ct.POINTER(ct.c_float)) self.lib.GetAdjustedRingExposureTimes(ct.c_int(n), outtimes) return times
[docs] def exposure_times(self, value): n = ct.c_int(len(value)) value = np.ascontiguousarray(value.astype(np.float)) outvalue = value.ctypes.data_as(ct.POINTER(ct.c_float)) self.lib.SetRingExposureTimes(n, outvalue)
@Feat(values={True: 1, False: 0}) def frame_transfer_mode(self): """This function will set whether an acquisition will readout in Frame Transfer Mode. If the acquisition mode is Single Scan or Fast Kinetics this call will have no affect. """ return self.frame_transfer_mode_state @frame_transfer_mode.setter def frame_transfer_mode(self, state): ans = self.lib.SetFrameTransferMode(ct.c_int(state)) if ans == 20002: self.frame_transfer_mode_state = state ### AMPLIFIERS, GAIN, SPEEDS @Feat(read_once=True) def n_preamps(self): """Available in some systems are a number of pre amp gains that can be applied to the data as it is read out. This function gets the number of these pre amp gains available. The functions GetPreAmpGain and SetPreAmpGain can be used to specify which of these gains is to be used. """ n = ct.c_int() self.lib.GetNumberPreAmpGains(ct.pointer(n)) return n.value
[docs] def preamp_available(self, channel, amp, index, preamp): """This function checks that the AD channel exists, and that the amplifier, speed and gain are available for the AD channel. """ channel = ct.c_int(channel) amp = ct.c_int(amp) index = ct.c_int(index) preamp = ct.c_int(preamp) status = ct.c_int() self.lib.IsPreAmpGainAvailable(channel, amp, index, preamp, ct.pointer(status)) return bool(status.value)
[docs] def preamp_descr(self, index): """This function will return a string with a pre amp gain description. The pre amp gain is selected using the index. The SDK has a string associated with each of its pre amp gains. The maximum number of characters needed to store the pre amp gain descriptions is 30. The user has to specify the number of characters they wish to have returned to them from this function. """ index = ct.c_int(index) descr = (ct.c_char * 30)() leng = ct.c_int(30) self.lib.GetAmpDesc(index, ct.pointer(descr), leng) return str(descr.value)[2:-1]
[docs] def true_preamp(self, index): """For those systems that provide a number of pre amp gains to apply to the data as it is read out; this function retrieves the amount of gain that is stored for a particular index. The number of gains available can be obtained by calling the GetNumberPreAmpGains function and a specific Gain can be selected using the function SetPreAmpGain. """ index = ct.c_int(index) gain = ct.c_float() self.lib.GetPreAmpGain(index, ct.pointer(gain)) return gain.value
@Feat() def preamp(self): """This function will set the pre amp gain to be used for subsequent acquisitions. The actual gain factor that will be applied can be found through a call to the GetPreAmpGain function. The number of Pre Amp Gains available is found by calling the GetNumberPreAmpGains function. """ return self.preamp_index @preamp.setter def preamp(self, index): self.preamp_index = index self.lib.SetPreAmpGain(ct.c_int(index)) @Feat(values={True: 1, False: 0}) def EM_advanced_enabled(self): """This function turns on and off access to higher EM gain levels within the SDK. Typically, optimal signal to noise ratio and dynamic range is achieved between x1 to x300 EM Gain. Higher gains of > x300 are recommended for single photon counting only. Before using higher levels, you should ensure that light levels do not exceed the regime of tens of photons per pixel, otherwise accelerated ageing of the sensor can occur. This is set to False upon initialization of the camera. """ state = ct.c_int() self.lib.GetEMAdvanced(ct.pointer(state)) return state.value @EM_advanced_enabled.setter def EM_advanced_enabled(self, value): self.lib.SetEMAdvanced(ct.c_int(value)) @Feat(values={'DAC255': 0, 'DAC4095': 1, 'Linear': 2, 'RealGain': 3}) def EM_gain_mode(self): """Set the EM Gain mode to one of the following possible settings. Mode 0: The EM Gain is controlled by DAC settings in the range 0-255. Default mode. 1: The EM Gain is controlled by DAC settings in the range 0-4095. 2: Linear mode. 3: Real EM gain """ return self.EM_gain_mode_index @EM_gain_mode.setter def EM_gain_mode(self, mode): ans = self.lib.SetEMGainMode(ct.c_int(mode)) if ans == 20002: self.EM_gain_mode_index = mode @Feat() def EM_gain(self): """Allows the user to change the gain value. The valid range for the gain depends on what gain mode the camera is operating in. See SetEMGainMode to set the mode and GetEMGainRange to get the valid range to work with. To access higher gain values (>x300) see SetEMAdvanced. """ gain = ct.c_int() self.lib.GetEMCCDGain(ct.pointer(gain)) return gain.value @EM_gain.setter def EM_gain(self, value): self.lib.SetEMCCDGain(ct.c_int(value)) @Feat() def EM_gain_range(self): """Returns the minimum and maximum values of the current selected EM Gain mode and temperature of the sensor. """ mini, maxi = ct.c_int(), ct.c_int() self.lib.GetEMGainRange(ct.pointer(mini), ct.pointer(maxi)) return (mini.value, maxi.value) @Feat(read_once=True) def n_ad_channels(self): n = ct.c_int() self.lib.GetNumberADChannels(ct.pointer(n)) return n.value @Feat(read_once=True) def n_amps(self): n = ct.c_int() self.lib.GetNumberAmp(ct.pointer(n)) return n.value
[docs] def amp_available(self, iamp): """This function checks if the hardware and current settings permit the use of the specified amplifier.""" ans = self.lib.IsAmplifierAvailable(ct.c_int(iamp)) if ans == 20002: return True else: return False
[docs] def amp_descr(self, index): """This function will return a string with an amplifier description. The amplifier is selected using the index. The SDK has a string associated with each of its amplifiers. The maximum number of characters needed to store the amplifier descriptions is 21. The user has to specify the number of characters they wish to have returned to them from this function. """ index = ct.c_int(index) descr = (ct.c_char * 21)() leng = ct.c_int(21) self.lib.GetAmpDesc(index, ct.pointer(descr), leng) return str(descr.value)[2:-1]
[docs] def readout_flipped(self, iamp): """On cameras with multiple amplifiers the frame readout may be flipped. This function can be used to determine if this is the case. """ flipped = ct.c_int() self.lib.IsReadoutFlippedByAmplifier(ct.c_int(iamp), ct.pointer(flipped)) return bool(flipped.value)
[docs] def amp_max_hspeed(self, index): """This function will return the maximum available horizontal shift speed for the amplifier selected by the index parameter. """ hspeed = ct.c_float() self.lib.GetAmpMaxSpeed(ct.c_int(index), ct.pointer(hspeed)) return hspeed.value
[docs] def n_horiz_shift_speeds(self, channel=0, typ=None): """As your Andor SDK system is capable of operating at more than one horizontal shift speed this function will return the actual number of speeds available. :param channel: the AD channel. :param typ: output amplification. 0 electron multiplication. 1 conventional. """ if typ is None: typ = self.amp_typ n = ct.c_int() self.lib.GetNumberHSSpeeds(ct.c_int(channel), ct.c_int(typ), ct.pointer(n)) return n.value
[docs] def true_horiz_shift_speed(self, index=0, typ=None, ad=0): """As your Andor system is capable of operating at more than one horizontal shift speed this function will return the actual speeds available. The value returned is in MHz. GetHSSpeed(int channel, int typ, int index, float* speed) :param typ: output amplification. 0 electron multiplication/Conventional(clara) 1 conventional/Extended NIR Mode(clara). :param index: speed required 0 to NumberSpeeds-1 where NumberSpeeds is value returned in first parameter after a call to GetNumberHSSpeeds(). :param ad: the AD channel. """ if typ is None: typ = self.amp_typ speed = ct.c_float() self.lib.GetHSSpeed(ct.c_int(ad), ct.c_int(typ), ct.c_int(index), ct.pointer(speed)) return speed.value * MHz
@Feat() def horiz_shift_speed(self): return self.horiz_shift_speed_index @horiz_shift_speed.setter def horiz_shift_speed(self, index): """This function will set the speed at which the pixels are shifted into the output node during the readout phase of an acquisition. Typically your camera will be capable of operating at several horizontal shift speeds. To get the actual speed that an index corresponds to use the GetHSSpeed function. :param typ: output amplification. 0 electron multiplication/Conventional(clara). 1 conventional/Extended NIR mode(clara). :param index: the horizontal speed to be used 0 to GetNumberHSSpeeds() - 1 """ ans = self.lib.SetHSSpeed(ct.c_int(self.amp_typ), ct.c_int(index)) if ans == 20002: self.horiz_shift_speed_index = index @Feat() def fastest_recommended_vsspeed(self): """As your Andor SDK system may be capable of operating at more than one vertical shift speed this function will return the fastest recommended speed available. The very high readout speeds, may require an increase in the amplitude of the Vertical Clock Voltage using SetVSAmplitude. This function returns the fastest speed which does not require the Vertical Clock Voltage to be adjusted. The values returned are the vertical shift speed index and the actual speed in microseconds per pixel shift. """ inti, f2 = ct.c_int(), ct.c_float() self.lib.GetFastestRecommendedVSSpeed(ct.pointer(inti), ct.pointer(f2)) return (inti.value, f2.value) @Feat(read_once=True) def n_vert_clock_amps(self): """This function will normally return the number of vertical clock voltage amplitudes that the camera has. """ n = ct.c_int() self.lib.GetNumberVSAmplitudes(ct.pointer(n)) return n.value
[docs] def vert_amp_index(self, string): """This Function is used to get the index of the Vertical Clock Amplitude that corresponds to the string passed in. :param string: "Normal" , "+1" , "+2" , "+3" , "+4" """ index = ct.c_int() string = ct.c_char_p(str.encode(string)) self.lib.GetVSAmplitudeFromString(string, ct.pointer(index)) return index.value
[docs] def vert_amp_string(self, index): """This Function is used to get the Vertical Clock Amplitude string that corresponds to the index passed in. :param index: Index of VS amplitude required Valid values 0 to GetNumberVSAmplitudes() - 1 """ index = ct.c_int(index) string = (ct.c_char * 6)() self.lib.GetVSAmplitudeString(index, ct.pointer(string)) return str(string.value)[2:-1]
[docs] def true_vert_amp(self, index): """This Function is used to get the value of the Vertical Clock Amplitude found at the index passed in. :param index: Index of VS amplitude required Valid values 0 to GetNumberVSAmplitudes() - 1 """ index = ct.c_int(index) amp = ct.c_int() self.lib.GetVSAmplitudeValue(index, ct.pointer(amp)) return amp.value
@Action() def set_vert_clock(self, index): """If you choose a high readout speed (a low readout time), then you should also consider increasing the amplitude of the Vertical Clock Voltage. There are five levels of amplitude available for you to choose from: - Normal, +1, +2, +3, +4 Exercise caution when increasing the amplitude of the vertical clock voltage, since higher clocking voltages may result in increased clock-induced charge (noise) in your signal. In general, only the very highest vertical clocking speeds are likely to benefit from an increased vertical clock voltage amplitude. """ self.lib.SetVSAmplitude(ct.c_int(index)) @Feat(read_once=True) def n_vert_shift_speeds(self): """As your Andor system may be capable of operating at more than one vertical shift speed this function will return the actual number of speeds available. """ n = ct.c_int() self.lib.GetNumberVSSpeeds(ct.pointer(n)) return n.value
[docs] def true_vert_shift_speed(self, index=0): """As your Andor SDK system may be capable of operating at more than one vertical shift speed this function will return the actual speeds available. The value returned is in microseconds. """ speed = ct.c_float() self.lib.GetVSSpeed(ct.c_int(index), ct.pointer(speed)) return speed.value * us
@Feat() def vert_shift_speed(self): return self.vert_shift_speed_index @vert_shift_speed.setter def vert_shift_speed(self, index): """This function will set the vertical speed to be used for subsequent acquisitions. """ self.vert_shift_speed_index = index self.lib.SetVSSpeed(ct.c_int(index)) ### BASELINE @Feat(values={True: 1, False: 0}) def baseline_clamp(self): """This function returns the status of the baseline clamp functionality. With this feature enabled the baseline level of each scan in a kinetic series will be more consistent across the sequence. """ i = ct.c_int() self.lib.GetBaselineClamp(ct.pointer(i)) return i.value @baseline_clamp.setter def baseline_clamp(self, value): value = ct.c_int(value) self.lib.SetBaselineClamp(value) @Feat(limits=(-1000, 1100, 100)) def baseline_offset(self): """This function allows the user to move the baseline level by the amount selected. For example “+100” will add approximately 100 counts to the default baseline value. The value entered should be a multiple of 100 between -1000 and +1000 inclusively. """ return self.baseline_offset_value @baseline_offset.setter def baseline_offset(self, value): ans = self.lib.SetBaselineOffset(ct.c_int(value)) if ans == 20002: self.baseline_offset_value = value ### BIT DEPTH
[docs] def bit_depth(self, ch): """This function will retrieve the size in bits of the dynamic range for any available AD channel. """ ch = ct.c_int(ch) depth = ct.c_uint() self.lib.GetBitDepth(ch, ct.pointer(depth)) return depth.value
### TRIGGER @Feat(values={True: 1, False: 0}) def adv_trigger_mode(self): """This function will set the state for the iCam functionality that some cameras are capable of. There may be some cases where we wish to prevent the software using the new functionality and just do it the way it was previously done. """ return self.adv_trigger_mode_state @adv_trigger_mode.setter def adv_trigger_mode(self, state): ans = self.lib.SetAdvancedTriggerModeState(ct.c_int(state)) if ans == 20002: self.adv_trigger_mode_state = state
[docs] def trigger_mode_available(self, modestr): """This function checks if the hardware and current settings permit the use of the specified trigger mode. """ index = self.triggers[modestr] ans = self.lib.IsTriggerModeAvailable(ct.c_int(index)) if ans == 20002: return True else: return False
@Feat(values={'Internal': 0, 'External': 1, 'External Start': 6, 'External Exposure': 7, 'External FVB EM': 9, 'Software Trigger': 10, 'External Charge Shifting': 12}) def trigger_mode(self): """This function will set the trigger mode that the camera will operate in. """ return self.trigger_mode_index @trigger_mode.setter def trigger_mode(self, mode): ans = self.lib.SetTriggerMode(ct.c_int(mode)) if ans == 20002: self.trigger_mode_index = mode @Action() def send_software_trigger(self): """This function sends an event to the camera to take an acquisition when in Software Trigger mode. Not all cameras have this mode available to them. To check if your camera can operate in this mode check the GetCapabilities function for the Trigger Mode AC_TRIGGERMODE_CONTINUOUS. If this mode is physically possible and other settings are suitable (IsTriggerModeAvailable) and the camera is acquiring then this command will take an acquisition. NOTES: The settings of the camera must be as follows: - ReadOut mode is full image - RunMode is Run Till Abort - TriggerMode is 10 """ self.lib.SendSoftwareTrigger() @Action() def trigger_level(self, value): """This function sets the trigger voltage which the system will use. """ self.lib.SetTriggerLevel(ct.c_float(value)) ### AUXPORT @DictFeat(values={True: not(0), False: 0}, keys=list(range(1, 5))) def in_aux_port(self, port): """This function returns the state of the TTL Auxiliary Input Port on the Andor plug-in card. """ port = ct.c_int(port) state = ct.c_int() self.lib.InAuxPort(port, ct.pointer(state)) return state.value @DictFeat(values={True: 1, False: 0}, keys=list(range(1, 5))) def out_aux_port(self, port): """This function sets the TTL Auxiliary Output port (P) on the Andor plug-in card to either ON/HIGH or OFF/LOW. """ return self.auxout[port - 1] @out_aux_port.setter def out_aux_port(self, port, state): self.auxout[port - 1] = bool(state) port = ct.c_int(port) state = ct.c_int(state) self.lib.OutAuxPort(port, ct.pointer(state))
[docs] def is_implemented(self, strcommand): """Checks if command is implemented. """ result = ct.c_bool() command = ct.c_wchar_p(strcommand) self.lib.AT_IsImplemented(self.AT_H, command, ct.addressof(result)) return result.value
[docs] def is_writable(self, strcommand): """Checks if command is writable. """ result = ct.c_bool() command = ct.c_wchar_p(strcommand) self.lib.AT_IsWritable(self.AT_H, command, ct.addressof(result)) return result.value
[docs] def queuebuffer(self, bufptr, value): """Put buffer in queue. """ value = ct.c_int(value) self.lib.AT_QueueBuffer(self.AT_H, ct.byref(bufptr), value)
[docs] def waitbuffer(self, ptr, bufsize): """Wait for next buffer ready. """ timeout = ct.c_int(20000) self.lib.AT_WaitBuffer(self.AT_H, ct.byref(ptr), ct.byref(bufsize), timeout)
[docs] def command(self, strcommand): """Run command. """ command = ct.c_wchar_p(strcommand) self.lib.AT_Command(self.AT_H, command)
[docs] def getint(self, strcommand): """Run command and get Int return value. """ result = ct.c_longlong() command = ct.c_wchar_p(strcommand) self.lib.AT_GetInt(self.AT_H, command, ct.addressof(result)) return result.value
[docs] def setint(self, strcommand, value): """SetInt function. """ command = ct.c_wchar_p(strcommand) value = ct.c_longlong(value) self.lib.AT_SetInt(self.AT_H, command, value)
[docs] def getfloat(self, strcommand): """Run command and get Int return value. """ result = ct.c_double() command = ct.c_wchar_p(strcommand) self.lib.AT_GetFloat(self.AT_H, command, ct.addressof(result)) return result.value
[docs] def setfloat(self, strcommand, value): """Set command with Float value parameter. """ command = ct.c_wchar_p(strcommand) value = ct.c_double(value) self.lib.AT_SetFloat(self.AT_H, command, value)
[docs] def getbool(self, strcommand): """Run command and get Bool return value. """ result = ct.c_bool() command = ct.c_wchar_p(strcommand) self.lib.AT_GetBool(self.AT_H, command, ct.addressof(result)) return result.value
[docs] def setbool(self, strcommand, value): """Set command with Bool value parameter. """ command = ct.c_wchar_p(strcommand) value = ct.c_bool(value) self.lib.AT_SetBool(self.AT_H, command, value)
[docs] def getenumerated(self, strcommand): """Run command and set Enumerated return value. """ result = ct.c_int() command = ct.c_wchar_p(strcommand) self.lib.AT_GetEnumerated(self.AT_H, command, ct.addressof(result))
[docs] def setenumerated(self, strcommand, value): """Set command with Enumerated value parameter. """ command = ct.c_wchar_p(strcommand) value = ct.c_bool(value) self.lib.AT_SetEnumerated(self.AT_H, command, value)
[docs] def setenumstring(self, strcommand, item): """Set command with EnumeratedString value parameter. """ command = ct.c_wchar_p(strcommand) item = ct.c_wchar_p(item) self.lib.AT_SetEnumString(self.AT_H, command, item)
[docs] def flush(self): self.lib.AT_Flush(self.AT_H)
if __name__ == '__main__': from matplotlib import pyplot as plt from lantz import Q_ import time degC = Q_(1, 'degC') us = Q_(1, 'us') MHz = Q_(1, 'MHz') s = Q_(1, 's') with CCD() as andor: print(andor.idn) andor.free_int_mem() # Acquisition settings andor.readout_mode = 'Image' andor.set_image() # andor.acquisition_mode = 'Single Scan' andor.acquisition_mode = 'Run till abort' andor.set_exposure_time(0.03 * s) andor.trigger_mode = 'Internal' andor.amp_typ = 0 andor.horiz_shift_speed = 0 andor.vert_shift_speed = 0 andor.shutter(0, 0, 0, 0, 0) # # Temperature stabilization # andor.temperature_setpoint = -30 * degC # andor.cooler_on = True # stable = 'Temperature has stabilized at set point.' # print('Temperature set point =', andor.temperature_setpoint) # while andor.temperature_status != stable: # print("Current temperature:", np.round(andor.temperature, 1)) # time.sleep(30) # print('Temperature has stabilized at set point') # Acquisition andor.start_acquisition() time.sleep(2) data = andor.most_recent_image(shape=andor.detector_shape) andor.abort_acquisition() plt.imshow(data, cmap='gray', interpolation='None') plt.colorbar() plt.show() print(data.min(), data.max(), data.mean())