import os, sys
from typing import Dict, List, Optional, Tuple, Any
from qcodes import Instrument, Parameter
from qcodes.utils.validators import Ints
from qcodes.utils.helpers import create_on_off_val_mapping
import ctypes
[docs]
class atmcd64d:
"""
Wrapper class for the atmcd64.dll Andor library.
The class has been tested for an Andor iDus DU401 BU2.
Args:
dll_path: Path to the atmcd64.dll file. If not set, a default path is used.
verbose: Flag for the verbose behaviour. If true, successful events are printed.
Attributes:
verbose: Flag for the verbose behaviour.
dll: WinDLL object for atmcd64.dll.
"""
# default dll path
_dll_path = 'C:\\Program Files\\Andor SDK\\atmcd64d.dll'
# success and error codes
_success_codes = {20002: 'DRV_SUCCESS', 20035: 'DRV_TEMP_NOT_STABILIZED', 20036: 'DRV_TEMPERATURE_STABILIZED',
20037: 'DRV_TEMPERATURE_NOT_REACHED'}
_error_codes = {
20001: 'DRV_ERROR_CODES', 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: 'DRV_ERROR_ACK', 20014: 'DRV_ERROR_UP_FIFO', 20015: 'DRV_ERROR_PATTERN',
20017: 'DRV_ACQUISITION_ERRORS', 20018: 'DRV_ACQ_BUFFER', 20019: 'DRV_ACQ_DOWNFIFO_FULL',
20020: 'DRV_PROC_UNKNOWN_INSTRUCTION', 20021: 'DRV_ILLEGAL_OP_CODE', 20022: 'DRV_KINETIC_TIME_NOT_MET',
20023: 'DRV_ACCUM_TIME_NOT_MET', 20024: 'DRV_NO_NEW_DATA', 20026: 'DRV_SPOOLERROR',
20027: 'DRV_SPOOLSETUPERROR', 20033: 'DRV_TEMPERATURE_CODES', 20034: 'DRV_TEMPERATURE_OFF',
20038: 'DRV_TEMPERATURE_OUT_RANGE', 20039: 'DRV_TEMPERATURE_NOT_SUPPORTED', 20040: 'DRV_TEMPERATURE_DRIFT',
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: 'DRV_P1INVALID', 20067: 'DRV_P2INVALID', 20068: 'DRV_P3INVALID', 20069: 'DRV_P4INVALID',
20070: 'DRV_INIERROR', 20071: 'DRV_COFERROR', 20072: 'DRV_ACQUIRING', 20073: 'DRV_IDLE',
20074: 'DRV_TEMPCYCLE', 20075: 'DRV_NOT_INITIALIZED', 20076: 'DRV_P5INVALID', 20077: 'DRV_P6INVALID',
20078: 'DRV_INVALID_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', 20990: 'DRV_ERROR_NOCAMERA',
20991: 'DRV_NOT_SUPPORTED', 20992: 'DRV_NOT_AVAILABLE', 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', 20099: 'DRV_BINNING_ERROR',
20100: 'DRV_INVALID_AMPLIFIER', 20101: 'DRV_INVALID_COUNTCONVERT_MODE'}
def __init__(self, dll_path: Optional[str] = None, verbose: bool = False):
if sys.platform != 'win32':
self.dll: Any = None
raise OSError("\"atmcd64d\" is only compatible with Microsoft Windows")
else:
self.dll = ctypes.windll.LoadLibrary(dll_path or self._dll_path)
self.verbose = verbose
[docs]
def error_check(self, code, function_name=''):
if code in self._success_codes.keys():
if self.verbose:
print("atmcd64d: [%s]: %s" % (function_name, self._success_codes[code]))
elif code in self._error_codes.keys():
print("atmcd64d: [%s]: %s" % (function_name, self._error_codes[code]))
raise Exception(self._error_codes[code])
else:
print("atmcd64d: [%s]: Unknown code: %s" % (function_name, code))
raise Exception()
[docs]
def cooler_off(self) -> None:
code = self.dll.CoolerOFF()
self.error_check(code, 'CoolerOFF')
[docs]
def cooler_on(self) -> None:
code = self.dll.CoolerON()
self.error_check(code, 'CoolerON')
[docs]
def get_acquired_data(self, size) -> List[int]:
c_data_array = ctypes.c_int * size
c_data = c_data_array()
code = self.dll.GetAcquiredData(ctypes.pointer(c_data), size)
self.error_check(code, 'GetAcquiredData')
acquired_data = []
for i in range(len(c_data)):
acquired_data.append(c_data[i])
return acquired_data
[docs]
def get_acquisition_timings(self) -> Tuple[float, float, float]:
c_exposure = ctypes.c_float()
c_accumulate = ctypes.c_float()
c_kinetic = ctypes.c_float()
code = self.dll.GetAcquisitionTimings(ctypes.byref(c_exposure), ctypes.byref(c_accumulate), ctypes.byref(c_kinetic))
self.error_check(code, 'GetAcquisitionTimings')
return c_exposure.value, c_accumulate.value, c_kinetic.value
[docs]
def get_camera_handle(self, camera_index) -> int:
c_camera_handle = ctypes.c_long()
code = self.dll.GetCameraHandle(camera_index, ctypes.byref(c_camera_handle))
self.error_check(code, 'GetCameraHandle')
return c_camera_handle.value
[docs]
def get_camera_serial_number(self) -> int:
c_serial_number = ctypes.c_int()
code = self.dll.GetCameraSerialNumber(ctypes.byref(c_serial_number))
self.error_check(code, 'GetCameraSerialNumber')
return c_serial_number.value
[docs]
def get_hardware_version(self) -> Tuple[int, int, int, int, int, int]:
c_pcb = ctypes.c_int()
c_decode = ctypes.c_int()
c_dummy1 = ctypes.c_int()
c_dummy2 = ctypes.c_int()
c_firmware_version = ctypes.c_int()
c_firmware_build = ctypes.c_int()
code = self.dll.GetHardwareVersion(ctypes.byref(c_pcb), ctypes.byref(c_decode), ctypes.byref(c_dummy1),
ctypes.byref(c_dummy2), ctypes.byref(c_firmware_version),
ctypes.byref(c_firmware_build))
self.error_check(code)
return c_pcb.value, c_decode.value, c_dummy1.value, c_dummy2.value, c_firmware_version.value, \
c_firmware_build.value
[docs]
def get_head_model(self) -> str:
c_head_model = ctypes.create_string_buffer(128)
code = self.dll.GetHeadModel(c_head_model)
self.error_check(code)
return c_head_model.value.decode('ascii')
[docs]
def get_detector(self) -> Tuple[int, int]:
c_x_pixels = ctypes.c_int()
c_y_pixels = ctypes.c_int()
code = self.dll.GetDetector(ctypes.byref(c_x_pixels), ctypes.byref(c_y_pixels))
self.error_check(code, 'GetDetector')
return c_x_pixels.value, c_y_pixels.value
[docs]
def get_filter_mode(self) -> int:
c_mode = ctypes.c_int()
code = self.dll.GetFilterMode(ctypes.byref(c_mode))
self.error_check(code, 'GetFilterMode')
return c_mode.value
[docs]
def get_status(self) -> int:
c_status = ctypes.c_int()
code = self.dll.GetStatus(ctypes.byref(c_status))
self.error_check(code, 'GetStatus')
return c_status.value
[docs]
def get_temperature(self) -> int:
c_temperature = ctypes.c_int()
code = self.dll.GetTemperature(ctypes.byref(c_temperature))
self.error_check(code, 'GetTemperature')
return c_temperature.value
[docs]
def get_temperature_range(self) -> Tuple[int, int]:
c_min_temp = ctypes.c_int()
c_max_temp = ctypes.c_int()
code = self.dll.GetTemperatureRange(ctypes.byref(c_min_temp), ctypes.byref(c_max_temp))
self.error_check(code, 'GetTemperatureRange')
return c_min_temp.value, c_max_temp.value
[docs]
def initialize(self, directory: str) -> None:
code = self.dll.Initialize(directory)
self.error_check(code, 'Initialize')
[docs]
def is_cooler_on(self) -> int:
c_cooler_status = ctypes.c_int()
code = self.dll.IsCoolerOn(ctypes.byref(c_cooler_status))
self.error_check(code, 'IsCoolerOn')
return c_cooler_status.value
[docs]
def set_accumulation_cycle_time(self, cycle_time: float) -> None:
c_cycle_time = ctypes.c_float(cycle_time)
code = self.dll.SetAccumulationCycleTime(c_cycle_time)
self.error_check(code, 'SetAccumulationCycleTime')
[docs]
def set_acquisition_mode(self, mode: int) -> None:
c_mode = ctypes.c_int(mode)
code = self.dll.SetAcquisitionMode(c_mode)
self.error_check(code, 'SetAcquisitionMode')
[docs]
def set_current_camera(self, camera_handle: int) -> None:
c_camera_handle = ctypes.c_long(camera_handle)
code = self.dll.SetCurrentCamera(c_camera_handle)
self.error_check(code, 'SetCurrentCamera')
[docs]
def set_exposure_time(self, exposure_time: float) -> None:
c_time = ctypes.c_float(exposure_time)
code = self.dll.SetExposureTime(c_time)
self.error_check(code, 'SetExposureTime')
[docs]
def set_filter_mode(self, mode: int) -> None:
c_mode = ctypes.c_int(mode)
code = self.dll.SetFilterMode(c_mode)
self.error_check(code, 'SetFilterMode')
[docs]
def set_number_accumulations(self, number: int) -> None:
c_number = ctypes.c_int(number)
code = self.dll.SetNumberAccumulations(c_number)
self.error_check(code, 'SetNumberAccumulations')
[docs]
def set_read_mode(self, mode: int) -> None:
code = self.dll.SetReadMode(mode)
self.error_check(code, 'SetReadMode')
[docs]
def set_shutter(self, typ: int, mode: int, closing_time: int, opening_time: int) -> None:
c_typ = ctypes.c_int(typ)
c_mode = ctypes.c_int(mode)
c_closing_time = ctypes.c_int(closing_time)
c_opening_time = ctypes.c_int(opening_time)
code = self.dll.SetShutter(c_typ, c_mode, c_closing_time, c_opening_time)
self.error_check(code, 'SetShutter')
[docs]
def set_temperature(self, temperature: int) -> None:
c_temperature = ctypes.c_int(temperature)
code = self.dll.SetTemperature(c_temperature)
self.error_check(code, 'SetTemperature')
[docs]
def set_trigger_mode(self, mode: int) -> None:
c_mode = ctypes.c_int(mode)
code = self.dll.SetTriggerMode(c_mode)
self.error_check(code, 'SetTriggerMode')
[docs]
def shut_down(self) -> None:
code = self.dll.ShutDown()
self.error_check(code, 'ShutDown')
[docs]
def start_acquisition(self) -> None:
code = self.dll.StartAcquisition()
self.error_check(code, 'StartAcquisition')
[docs]
def wait_for_acquisition(self) -> None:
code = self.dll.WaitForAcquisition()
self.error_check(code, 'WaitForAcquisition')
[docs]
class Spectrum(Parameter):
"""
Parameter class for a spectrum taken with an Andor CCD.
The spectrum is saved in a list with the length being set by the number of pixels on the CCD.
Args:
name: Parameter name.
"""
def __init__(self, name: str, instrument: "Andor_DU401", **kwargs):
super().__init__(name, instrument=instrument, **kwargs)
self.ccd = instrument
[docs]
def get_raw(self) -> List[int]:
# get acquisition mode
acquisition_mode = self.ccd.acquisition_mode.get()
# start acquisition
self.ccd.atmcd64d.start_acquisition()
if acquisition_mode == 'single scan':
# wait for single acquisition
self.ccd.atmcd64d.wait_for_acquisition()
elif acquisition_mode == 'accumulate':
# wait for accumulate acquisition
number_accumulations = self.ccd.number_accumulations.get()
for i in range(number_accumulations):
self.ccd.atmcd64d.wait_for_acquisition()
# get and return spectrum
return self.ccd.atmcd64d.get_acquired_data(self.ccd.x_pixels)
[docs]
def set_raw(self, value):
raise NotImplementedError()
[docs]
class Andor_DU401(Instrument):
"""
Instrument driver for the Andor DU401 BU2 CCD.
Args:
name: Instrument name.
dll_path: Path to the atmcd64.dll file. If not set, a default path is used.
camera_id: ID for the desired CCD.
setup: Flag for the setup of the CCD. If true, some default settings will be sent to the CCD.
Attributes:
serial_number: Serial number of the CCD.
head_model: Head model of the CCD.
firmware_version: Firmware version of the CCD.
firmware_build: Firmware build of the CCD.
x_pixels: Number of pixels on the x axis.
y_pixels: Number of pixels on the y axis.
"""
# TODO (SvenBo90): implement further acquisition modes
# TODO (SvenBo90): implement further read modes
# TODO (SvenBo90): implement further trigger modes
# TODO (SvenBo90): add and delete parameters dynamically when switching acquisition mode or read mode
# TODO (SvenBo90): handle shutter closing and opening timings
def __init__(self, name: str, dll_path: Optional[str] = None, camera_id: int = 0, setup: bool = True, **kwargs):
super().__init__(name, **kwargs)
# link to dll
self.atmcd64d = atmcd64d(dll_path=dll_path)
# initialization
self.atmcd64d.initialize(' ')
self.atmcd64d.set_current_camera(self.atmcd64d.get_camera_handle(camera_id))
# get camera information
self.serial_number = self.atmcd64d.get_camera_serial_number()
self.head_model = self.atmcd64d.get_head_model()
self.firmware_version = self.atmcd64d.get_hardware_version()[4]
self.firmware_build = self.atmcd64d.get_hardware_version()[5]
self.x_pixels, self.y_pixels = self.atmcd64d.get_detector()
# add the instrument parameters
self.add_parameter('accumulation_cycle_time',
get_cmd=self.atmcd64d.get_acquisition_timings,
set_cmd=self.atmcd64d.set_accumulation_cycle_time,
get_parser=lambda ans: float(ans[1]),
unit='s',
label='accumulation cycle time')
self.add_parameter('acquisition_mode',
set_cmd=self.atmcd64d.set_acquisition_mode,
val_mapping={
'single scan': 1,
'accumulate': 2
},
label='acquisition mode')
self.add_parameter('cooler',
get_cmd=self.atmcd64d.is_cooler_on,
set_cmd=self._set_cooler,
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
label='cooler')
self.add_parameter('exposure_time',
get_cmd=self.atmcd64d.get_acquisition_timings,
set_cmd=self.atmcd64d.set_exposure_time,
get_parser=lambda ans: float(ans[0]),
unit='s',
label='exposure time')
self.add_parameter('filter_mode',
get_cmd=self.atmcd64d.get_filter_mode,
set_cmd=self.atmcd64d.set_filter_mode,
val_mapping=create_on_off_val_mapping(on_val=2, off_val=0),
label='filter mode')
self.add_parameter('number_accumulations',
set_cmd=self.atmcd64d.set_number_accumulations,
label='number accumulations')
self.add_parameter('read_mode',
set_cmd=self.atmcd64d.set_read_mode,
val_mapping={'full vertical binning': 0})
min_temperature, max_temperature = self.atmcd64d.get_temperature_range()
self.add_parameter('set_temperature',
set_cmd=self.atmcd64d.set_temperature,
vals=Ints(min_value=min_temperature,
max_value=max_temperature),
unit=u"\u00b0"+'C',
label='set temperature')
self.add_parameter('shutter_mode',
set_cmd=self._set_shutter_mode,
val_mapping={
'fully auto': 0,
'permanently open': 1,
'permanently closed': 2},
label='shutter mode')
self.add_parameter('spectrum',
parameter_class=Spectrum,
shape=(1, self.x_pixels),
label='spectrum')
self.add_parameter('temperature',
get_cmd=self.atmcd64d.get_temperature,
unit=u"\u00b0"+'C',
label='temperature')
self.add_parameter('trigger_mode',
set_cmd=self.atmcd64d.set_trigger_mode,
val_mapping={'internal': 0})
# set up detector with default settings
if setup:
self.cooler.set(True)
self.set_temperature.set(-60)
self.read_mode.set('full vertical binning')
self.acquisition_mode.set('single scan')
self.trigger_mode.set('internal')
self.shutter_mode.set('fully auto')
# print connect message
self.connect_message(idn_param='IDN')
# get methods
[docs]
def get_idn(self) -> Dict[str, Optional[str]]:
return {'vendor': 'Andor', 'model': self.head_model,
'serial': str(self.serial_number), 'firmware': str(self.firmware_version)+'.'+str(self.firmware_build)}
# set methods
def _set_cooler(self, cooler_on: int) -> None:
if cooler_on == 1:
self.atmcd64d.cooler_on()
elif cooler_on == 0:
self.atmcd64d.cooler_off()
def _set_shutter_mode(self, shutter_mode: int) -> None:
self.atmcd64d.set_shutter(1, shutter_mode, 30, 30)
# further methods
[docs]
def close(self) -> None:
self.atmcd64d.shut_down()
super().close()