1091 lines
49 KiB
Python
1091 lines
49 KiB
Python
from .base import AndorError, AndorTimeoutError, AndorNotSupportedError
|
|
from . import atmcd32d_lib
|
|
from .atmcd32d_lib import wlib as lib, AndorSDK2LibError
|
|
from .atmcd32d_lib import DRV_STATUS
|
|
from .atmcd32d_lib import AC_ACQMODE, AC_READMODE, AC_TRIGGERMODE, AC_EMGAIN, AC_FEATURES, AC_GETFUNC, AC_SETFUNC
|
|
from .atmcd32d_lib import drAC_CAMERATYPE, AC_PIXELMODE
|
|
from .atmcd32d_defs import AT_VersionInfoId
|
|
|
|
from ...core.utils import py3
|
|
from ...core.devio import interface
|
|
from ..interface import camera
|
|
from ..utils import load_lib
|
|
|
|
import numpy as np
|
|
import collections
|
|
import functools
|
|
import threading
|
|
|
|
|
|
|
|
class LibraryController(load_lib.LibraryController):
|
|
def _do_uninit(self):
|
|
try:
|
|
self.lib.ShutDown()
|
|
except AndorSDK2LibError as e:
|
|
if e.code!=DRV_STATUS.DRV_NOT_INITIALIZED:
|
|
raise
|
|
libctl=LibraryController(lib)
|
|
def restart_lib():
|
|
libctl.shutdown()
|
|
|
|
|
|
def get_SDK_version():
|
|
"""Get version of Andor SDK2"""
|
|
libctl.preinit()
|
|
return py3.as_str(lib.GetVersionInfo(AT_VersionInfoId.AT_SDKVersion))
|
|
def get_cameras_number():
|
|
"""Get number of connected Andor cameras"""
|
|
libctl.preinit()
|
|
return lib.GetAvailableCameras()
|
|
|
|
|
|
TDeviceInfo=collections.namedtuple("TDeviceInfo",["controller_model","head_model","serial_number"])
|
|
TCycleTimings=collections.namedtuple("TCycleTimings",["exposure","accum_cycle_time","kinetic_cycle_time"])
|
|
TAcqProgress=collections.namedtuple("TAcqProgress",["frames_done","cycles_done"])
|
|
|
|
_camsel_lock=threading.RLock()
|
|
def _camfunc(*args, **kwargs):
|
|
if len(args)>0:
|
|
return _camfunc(**kwargs)(args[0])
|
|
option=kwargs.get("option",[])
|
|
if not isinstance(option,list):
|
|
option=[option]
|
|
sel=kwargs.get("sel",True)
|
|
setpar=kwargs.get("setpar",None)
|
|
getpar=kwargs.get("getpar",None)
|
|
defpar=kwargs.get("defpar",None)
|
|
par_lookup=(getpar is not None) and not option
|
|
if (setpar is not None) and (getpar is not None):
|
|
raise ValueError("a method is either a setpar or a getpar")
|
|
def wrapper(func):
|
|
@functools.wraps(func)
|
|
def wrapped(self, *args, **kwargs):
|
|
if par_lookup:
|
|
return self._cpar.get(getpar,defpar)
|
|
with _camsel_lock:
|
|
if sel:
|
|
self._select_camera()
|
|
for opt in option:
|
|
if not self._check_option(*opt):
|
|
return defpar
|
|
if getpar is not None:
|
|
return self._cpar.get(getpar,defpar)
|
|
else:
|
|
res=func(self,*args,**kwargs)
|
|
if setpar is not None:
|
|
self._cpar[setpar]=res
|
|
return res
|
|
return wrapped
|
|
return wrapper
|
|
class AndorSDK2Camera(camera.IBinROICamera, camera.IExposureCamera):
|
|
"""
|
|
Andor SDK2 camera.
|
|
|
|
Due to the library features, the camera needs to set up all of the parameters to some default values upon connection.
|
|
Most of these parameters are chosen as reasonable defaults: full ROI, minimal exposure time, closed shutter,
|
|
internal trigger, fastest recommended verticals shift speed, no EMCCD gain.
|
|
However, some should be supplied during the connection: temperature setpoint (where appropriate), fan mode, and amplifier mode;
|
|
while there is still a possibility to have default values of these parameters, they might not be appropriate in some settings, and frequently need to be changed.
|
|
|
|
Caution: the manufacturer DLL is designed such that if the camera is not closed on the program termination, the allocated resources are never released.
|
|
If this happens, these resources are blocked until the complete OS restart.
|
|
|
|
Args:
|
|
idx(int): camera index (use :func:`get_cameras_number` to get the total number of connected cameras)
|
|
ini_path(str): path to .ini file, if required by the camera
|
|
temperature: initial temperature setpoint (in C); can also be ``None`` (select the bottom 20% of the whole range),
|
|
or ``"off"`` (turn the cooler off and set the maximal of the whole range)
|
|
fan_mode: initial fan mode
|
|
amp_mode: initial amplifier mode (a tuple like the one returned by :meth:`get_amp_mode`);
|
|
can also be ``None``, which selects the slowest, smallest gain mode
|
|
"""
|
|
_default_image_indexing="rcb"
|
|
Error=AndorError
|
|
TimeoutError=AndorTimeoutError
|
|
def __init__(self, idx=0, ini_path="", temperature=None, fan_mode="off", amp_mode=None):
|
|
super().__init__()
|
|
self.idx=idx
|
|
self.ini_path=ini_path
|
|
self.handle=None
|
|
self._opid=None
|
|
self.capabilities=None
|
|
self._minh=0
|
|
self._minv=0
|
|
self._cpar={}
|
|
self._start_temperature=temperature
|
|
self._start_fan_mode=fan_mode
|
|
self._start_amp_mode=amp_mode
|
|
self.open()
|
|
|
|
self._device_var_ignore_error={"get":(AndorNotSupportedError,),"set":(AndorNotSupportedError,)}
|
|
self._add_info_variable("device_info",self.get_device_info)
|
|
self._add_info_variable("capabilities",self.get_capabilities,priority=-5)
|
|
self._add_info_variable("amp_modes",self.get_all_amp_modes,ignore_error=AndorSDK2LibError,priority=-2)
|
|
self._add_info_variable("vsspeeds",self.get_all_vsspeeds,ignore_error=AndorSDK2LibError,priority=-2)
|
|
self._add_info_variable("pixel_size",self.get_pixel_size)
|
|
self._add_settings_variable("temperature",self.get_temperature_setpoint,self.set_temperature)
|
|
self._add_status_variable("temperature_monitor",self.get_temperature,ignore_error=AndorSDK2LibError)
|
|
self._add_status_variable("temperature_status",self.get_temperature_status,ignore_error=AndorSDK2LibError)
|
|
self._add_info_variable("temperature_range",self.get_temperature_range,ignore_error=AndorSDK2LibError,priority=-2)
|
|
self._add_settings_variable("cooler",self.is_cooler_on,self.set_cooler,ignore_error=AndorSDK2LibError)
|
|
self._add_status_variable("amp_mode",self.get_amp_mode,ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("channel",self.get_channel,lambda x:self.set_amp_mode(channel=x))
|
|
self._add_settings_variable("oamp",self.get_oamp,lambda x:self.set_amp_mode(oamp=x))
|
|
self._add_settings_variable("hsspeed",self.get_hsspeed,lambda x:self.set_amp_mode(hsspeed=x))
|
|
self._add_settings_variable("preamp",self.get_preamp,lambda x:self.set_amp_mode(preamp=x),ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("vsspeed",self.get_vsspeed,self.set_vsspeed,ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("EMCCD_gain",self.get_EMCCD_gain,self.set_EMCCD_gain,ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("shutter",self.get_shutter_parameters,self.setup_shutter,ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("fan_mode",self.get_fan_mode,self.set_fan_mode,ignore_error=AndorSDK2LibError)
|
|
self._add_settings_variable("trigger_mode",self.get_trigger_mode,self.set_trigger_mode)
|
|
self._add_settings_variable("acq_parameters/accum",self.get_accum_mode_parameters,self.setup_accum_mode)
|
|
self._add_settings_variable("acq_parameters/kinetic",self.get_kinetic_mode_parameters,self.setup_kinetic_mode)
|
|
self._add_settings_variable("acq_parameters/fast_kinetic",self.get_fast_kinetic_mode_parameters,self.setup_fast_kinetic_mode)
|
|
self._add_settings_variable("acq_parameters/cont",self.get_cont_mode_parameters,self.setup_cont_mode)
|
|
self._add_settings_variable("acq_mode",self.get_acquisition_mode,self.set_acquisition_mode)
|
|
self._add_status_variable("acq_status",self.get_status)
|
|
self._add_settings_variable("frame_transfer",self.is_frame_transfer_enabled,self.enable_frame_transfer_mode,ignore_error=AndorSDK2LibError)
|
|
self._add_status_variable("cycle_timings",self.get_cycle_timings)
|
|
self._add_status_variable("readout_time",self.get_readout_time)
|
|
self._add_settings_variable("read_parameters/single_track",self.get_single_track_mode_parameters,self.setup_single_track_mode)
|
|
self._add_settings_variable("read_parameters/multi_track",self.get_multi_track_mode_parameters,self.setup_multi_track_mode)
|
|
self._add_settings_variable("read_parameters/random_track",self.get_random_track_mode_parameters,self.setup_random_track_mode)
|
|
self._add_settings_variable("read_parameters/image",self.get_image_mode_parameters,self.setup_image_mode)
|
|
self._add_settings_variable("read_mode",self.get_read_mode,self.set_read_mode)
|
|
self._update_device_variable_order("exposure")
|
|
self._add_settings_variable("frame_period",self.get_frame_period,self.set_frame_period)
|
|
|
|
def _initial_setup_temperature(self):
|
|
if self._start_temperature=="off":
|
|
trng=self.get_temperature_range()
|
|
self.set_temperature(trng[1] if trng else 0,enable_cooler=False)
|
|
if self._start_temperature is None:
|
|
trng=self.get_temperature_range()
|
|
if trng:
|
|
self._start_temperature=trng[0]+int((trng[1]-trng[0])*0.2)
|
|
else:
|
|
self._start_temperature=0
|
|
self.set_temperature(self._start_temperature,enable_cooler=True)
|
|
def _initial_setup_ext_trigger(self):
|
|
try:
|
|
lrng=self.get_trigger_level_limits()
|
|
level=(lrng[0]+lrng[1])/2
|
|
except AndorNotSupportedError:
|
|
level=None
|
|
self.setup_ext_trigger(level,False,True)
|
|
def _setup_default_settings(self):
|
|
self.capabilities=self._get_capabilities_n()
|
|
self.device_info=self.get_device_info()
|
|
try:
|
|
self._strict_option_check=False
|
|
self._initial_setup_temperature()
|
|
self.init_amp_mode(mode=self._start_amp_mode)
|
|
self.set_fan_mode(self._start_fan_mode)
|
|
self.setup_shutter("closed")
|
|
self._initial_setup_ext_trigger()
|
|
self.set_trigger_mode("int")
|
|
self.set_exposure(0)
|
|
self.set_acquisition_mode("cont")
|
|
self.setup_accum_mode(1)
|
|
self.setup_kinetic_mode(1)
|
|
self.setup_fast_kinetic_mode(1)
|
|
self.setup_cont_mode()
|
|
self.enable_frame_transfer_mode(False)
|
|
self.setup_single_track_mode()
|
|
for (height,offset) in [(1,0),(2,0),(1,1),(2,1),(self.get_detector_size()[1],0),(self.get_detector_size()[1],1)]:
|
|
try:
|
|
self.setup_multi_track_mode(height=height,offset=offset)
|
|
break
|
|
except AndorSDK2LibError:
|
|
pass
|
|
self.setup_random_track_mode()
|
|
self._minh=self._find_min_roi_end("h")
|
|
self._minv=self._find_min_roi_end("v")
|
|
self.setup_image_mode()
|
|
self.setup_acquisition("single") # flush the buffers
|
|
self.setup_acquisition("cont")
|
|
self.clear_acquisition()
|
|
self._buffer_size=0
|
|
finally:
|
|
self._strict_option_check=True
|
|
|
|
def _select_camera(self):
|
|
if self.handle is None:
|
|
raise AndorError("camera is not opened")
|
|
if lib.GetCurrentCamera()!=self.handle:
|
|
lib.SetCurrentCamera(self.handle)
|
|
def _has_option(self, kind, option):
|
|
if kind=="acq":
|
|
opt=self.capabilities.ulAcqModes
|
|
elif kind=="read":
|
|
opt=self.capabilities.ulReadModes
|
|
elif kind=="trig":
|
|
opt=self.capabilities.ulTriggerModes
|
|
elif kind=="set":
|
|
opt=self.capabilities.ulSetFunctions
|
|
elif kind=="get":
|
|
opt=self.capabilities.ulGetFunctions
|
|
elif kind=="feat":
|
|
opt=self.capabilities.ulFeatures
|
|
else:
|
|
raise AndorError("unknown option kind: {}".format(kind))
|
|
return bool(option&opt)
|
|
def _check_option(self, kind, option):
|
|
has_option=self._has_option(kind,option)
|
|
if (not has_option) and self._strict_option_check:
|
|
raise AndorNotSupportedError("option {}.{} is not supported by {}".format(kind,getattr(option,"name",option),self.device_info.head_model))
|
|
return has_option
|
|
|
|
def _get_connection_parameters(self):
|
|
return self.idx,self.ini_path
|
|
def open(self):
|
|
"""Open connection to the camera"""
|
|
if self.handle is None:
|
|
ncams=get_cameras_number()
|
|
if self.idx>=ncams:
|
|
raise AndorError("camera index {} is not available ({} cameras exist)".format(self.idx,ncams))
|
|
self.handle=lib.GetCameraHandle(self.idx)
|
|
with self._close_on_error():
|
|
with _camsel_lock:
|
|
self._select_camera()
|
|
lib.Initialize(py3.as_builtin_bytes(self.ini_path))
|
|
self._opid=libctl.open().opid
|
|
self._setup_default_settings()
|
|
def close(self):
|
|
"""Close connection to the camera"""
|
|
if self.handle is not None:
|
|
try:
|
|
if self._opid is not None and self.capabilities is not None:
|
|
self.clear_acquisition()
|
|
try:
|
|
self._select_camera()
|
|
except AndorError:
|
|
pass
|
|
finally:
|
|
self.handle=None
|
|
libctl.close(self._opid)
|
|
self._opid=None
|
|
def is_opened(self):
|
|
"""Check if the device is connected"""
|
|
return self.handle is not None
|
|
|
|
@_camfunc
|
|
def get_device_info(self):
|
|
"""
|
|
Get camera device info.
|
|
|
|
Return tuple ``(controller_mode, head_model, serial_number)``.
|
|
"""
|
|
control_model=py3.as_str(lib.GetControllerCardModel())
|
|
head_model=py3.as_str(lib.GetHeadModel())
|
|
serial_number=lib.GetCameraSerialNumber()
|
|
return TDeviceInfo(control_model,head_model,serial_number)
|
|
|
|
|
|
### Generic controls ###
|
|
_p_status=interface.EnumParameterClass("status",
|
|
{"idle":DRV_STATUS.DRV_IDLE,"acquiring":DRV_STATUS.DRV_ACQUIRING,"temp_cycle":DRV_STATUS.DRV_TEMPCYCLE})
|
|
@_camfunc(option=("feat",AC_FEATURES.AC_FEATURES_POLLING))
|
|
@interface.use_parameters(_returns="status")
|
|
def get_status(self):
|
|
"""
|
|
Get camera status.
|
|
|
|
Return either ``"idle"`` (no acquisition), ``"acquiring"`` (acquisition in progress) or ``"temp_cycle"`` (temperature cycle in progress).
|
|
"""
|
|
status=lib.GetStatus()
|
|
if status not in {DRV_STATUS.DRV_IDLE,DRV_STATUS.DRV_ACQUIRING,DRV_STATUS.DRV_TEMPCYCLE}:
|
|
raise AndorSDK2LibError("GetStatus",status)
|
|
return status
|
|
def acquisition_in_progress(self):
|
|
return self.get_status()=="acquiring"
|
|
def _int_to_enumlst(self, value, enums):
|
|
lst=[]
|
|
for k in enums:
|
|
if value&k:
|
|
lst.append(enums(k).name)
|
|
return lst
|
|
def _parse_capabilities(self, caps):
|
|
cap_dict={}
|
|
cap_dict["acq_mode"]=self._int_to_enumlst(caps.ulAcqModes,AC_ACQMODE)
|
|
cap_dict["read_mode"]=self._int_to_enumlst(caps.ulReadModes,AC_READMODE)
|
|
cap_dict["ft_read_mode"]=self._int_to_enumlst(caps.ulFTReadModes,AC_READMODE)
|
|
cap_dict["trig_mode"]=self._int_to_enumlst(caps.ulTriggerModes,AC_TRIGGERMODE)
|
|
cap_dict["set_func"]=self._int_to_enumlst(caps.ulSetFunctions,AC_SETFUNC)
|
|
cap_dict["get_func"]=self._int_to_enumlst(caps.ulGetFunctions,AC_GETFUNC)
|
|
cap_dict["features"]=self._int_to_enumlst(caps.ulFeatures,AC_FEATURES)
|
|
cap_dict["EM_gain"]=self._int_to_enumlst(caps.ulEMGainCapability,AC_EMGAIN)
|
|
cap_dict["pci_speed"]=int(caps.ulPCICard)
|
|
cap_dict["pix_mode"]=self._int_to_enumlst(caps.ulPixelMode&0xFFFF,AC_PIXELMODE),AC_PIXELMODE(caps.ulPixelMode&0xFFFF0000).name
|
|
cap_dict["cam_type"]=drAC_CAMERATYPE.get(caps.ulCameraType,"UNKNOWN")
|
|
return cap_dict
|
|
@_camfunc
|
|
def _get_capabilities_n(self):
|
|
"""Get camera capabilities as a plain ``AndorCapabilities`` structure"""
|
|
return lib.GetCapabilities()
|
|
def get_capabilities(self):
|
|
"""
|
|
Get camera capabilities.
|
|
|
|
For description of the structure, see Andor SDK manual.
|
|
"""
|
|
return self._parse_capabilities(self._get_capabilities_n())
|
|
@_camfunc
|
|
def get_pixel_size(self):
|
|
"""Get camera pixel size (in m)"""
|
|
return tuple([s*1E-6 for s in lib.GetPixelSize()])
|
|
|
|
### Cooler controls ###
|
|
@_camfunc(option=("get",AC_SETFUNC.AC_SETFUNCTION_TEMPERATURE))
|
|
def is_cooler_on(self):
|
|
"""Check if the cooler is on"""
|
|
return bool(lib.IsCoolerOn())
|
|
@_camfunc(option=("set",AC_GETFUNC.AC_GETFUNCTION_TEMPERATURE))
|
|
def set_cooler(self, on=True):
|
|
"""Set the cooler on or off"""
|
|
if on:
|
|
lib.CoolerON()
|
|
else:
|
|
lib.CoolerOFF()
|
|
return self.is_cooler_on()
|
|
|
|
|
|
_p_temp_status=interface.EnumParameterClass("temp_status",{
|
|
"off":DRV_STATUS.DRV_TEMPERATURE_OFF, "not_reached":DRV_STATUS.DRV_TEMPERATURE_NOT_REACHED, "not_stabilized":DRV_STATUS.DRV_TEMPERATURE_NOT_STABILIZED,
|
|
"drifted":DRV_STATUS.DRV_TEMPERATURE_DRIFT, "stabilized":DRV_STATUS.DRV_TEMPERATURE_STABILIZED })
|
|
@_camfunc(option=("get",AC_GETFUNC.AC_GETFUNCTION_TEMPERATURE))
|
|
@interface.use_parameters(_returns="temp_status")
|
|
def get_temperature_status(self):
|
|
"""
|
|
Get temperature status.
|
|
|
|
Can return ``"off"`` (cooler off), ``"not_reached"`` (cooling in progress), ``"not_stabilized"`` (reached but not stabilized yet),
|
|
``"stabilized"`` (completely stabilized) or ``"drifted"``.
|
|
"""
|
|
return lib.GetTemperatureF()[0]
|
|
@_camfunc(option=("get",AC_GETFUNC.AC_GETFUNCTION_TEMPERATURE))
|
|
def get_temperature(self):
|
|
"""Get the current camera temperature (in C)"""
|
|
return lib.GetTemperatureF()[1]
|
|
@_camfunc(setpar="temperature_setpoint",option=[("get",AC_GETFUNC.AC_GETFUNCTION_TEMPERATURERANGE),("set",AC_SETFUNC.AC_SETFUNCTION_TEMPERATURE)])
|
|
def set_temperature(self, temperature, enable_cooler=True):
|
|
"""
|
|
Change the temperature setpoint (in C).
|
|
|
|
If ``enable_cooler==True``, turn the cooler on automatically if present.
|
|
"""
|
|
rng=lib.GetTemperatureRange()
|
|
temperature=max(temperature,rng[0])
|
|
temperature=min(temperature,rng[1])
|
|
temperature=int(temperature)
|
|
lib.SetTemperature(temperature)
|
|
if enable_cooler:
|
|
try:
|
|
self.set_cooler(True)
|
|
except AndorSDK2LibError:
|
|
pass
|
|
return temperature
|
|
@_camfunc(getpar="temperature_setpoint",option=[("set",AC_SETFUNC.AC_SETFUNCTION_TEMPERATURE)])
|
|
def get_temperature_setpoint(self):
|
|
"""Get the temperature setpoint (in C)"""
|
|
@_camfunc
|
|
def get_temperature_range(self):
|
|
"""Return the available range of temperatures (in C)"""
|
|
return lib.GetTemperatureRange()
|
|
|
|
### Amplifiers/shift speeds controls ###
|
|
@_camfunc
|
|
def get_all_amp_modes(self):
|
|
"""
|
|
Get all available preamp modes.
|
|
|
|
Each preamp mode is characterized by an AD channel index, amplifier index, channel speed (horizontal scan speed) index and preamp gain index.
|
|
Return list of tuples ``(channel, channel_bitdepth, oamp, oamp_kind, hsspeed, hsspeed_MHz, preamp, preamp_gain)``,
|
|
where ``channel``, ``oamp``, ``hsspeed`` and ``preamp`` are indices, while ``channel_bitdepth``, ``oamp_kind``, ``hsspeed_MHz`` and ``preamp_gain`` are descriptions.
|
|
"""
|
|
return lib.get_all_amp_modes()
|
|
@_camfunc
|
|
def get_max_vsspeed(self):
|
|
"""Get maximal recommended vertical scan speed"""
|
|
return lib.GetFastestRecommendedVSSpeed()[0]
|
|
@_camfunc
|
|
def get_all_vsspeeds(self):
|
|
"""
|
|
Get all available vertical shift speeds modes.
|
|
|
|
Return list of the vertical shift periods in microseconds for the corresponding indices (starting from 0).
|
|
"""
|
|
try:
|
|
nspeeds=lib.GetNumberVSSpeeds()
|
|
return [lib.GetVSSpeed(i) for i in range(nspeeds)]
|
|
except AndorSDK2LibError:
|
|
return []
|
|
def _truncate_amp_mode(self, amp_mode):
|
|
all_amp_modes=self.get_all_amp_modes()
|
|
all_amp_modes=[(mode.channel,mode.oamp,mode.hsspeed,mode.preamp) for mode in all_amp_modes]
|
|
for i,v in enumerate(amp_mode):
|
|
if v is None:
|
|
continue
|
|
avs={mode[i] for mode in all_amp_modes}
|
|
if v not in avs:
|
|
lvs=[vv for vv in avs if vv<v]
|
|
v=max(lvs) if lvs else min(avs)
|
|
all_amp_modes=[mode for mode in all_amp_modes if mode[i]==v]
|
|
return all_amp_modes[0]
|
|
@_camfunc
|
|
def set_amp_mode(self, channel=None, oamp=None, hsspeed=None, preamp=None):
|
|
"""
|
|
Setup preamp mode.
|
|
|
|
Can specify AD channel index, amplifier index, channel speed (horizontal scan speed) index and preamp gain index.
|
|
``None`` (default) means leaving the current value.
|
|
"""
|
|
channel=self._cpar["channel"] if channel is None else channel
|
|
oamp=self._cpar["oamp"] if oamp is None else oamp
|
|
hsspeed=self._cpar["hsspeed"] if hsspeed is None else hsspeed
|
|
preamp=self._cpar["preamp"] if preamp is None else preamp
|
|
if not self._has_option("set",AC_SETFUNC.AC_SETFUNCTION_PREAMPGAIN):
|
|
preamp=None
|
|
if not self._has_option("set",AC_SETFUNC.AC_SETFUNCTION_HREADOUT):
|
|
hsspeed=None
|
|
channel,oamp,hsspeed,preamp=self._truncate_amp_mode((channel,oamp,hsspeed,preamp))
|
|
lib.set_amp_mode((channel,oamp,hsspeed,preamp))
|
|
self._cpar["channel"]=channel
|
|
self._cpar["oamp"]=oamp
|
|
self._cpar["hsspeed"]=hsspeed
|
|
self._cpar["preamp"]=preamp
|
|
def get_amp_mode(self, full=True):
|
|
"""
|
|
Return the current amplifier mode.
|
|
|
|
If ``full==True``, return a full description (e.g., actual preamp gain or channel name);
|
|
otherwise, return just the essential indices information (enough to set the mode for this camera, but no explanations).
|
|
"""
|
|
mode=atmcd32d_lib.TAmpModeSimple(self.get_channel(),self.get_oamp(),self.get_hsspeed(),self.get_preamp())
|
|
if full:
|
|
mode=lib.get_amp_mode_description(mode)
|
|
return mode
|
|
@_camfunc(setpar="vsspeed",option=[("set",AC_SETFUNC.AC_SETFUNCTION_VREADOUT)])
|
|
def set_vsspeed(self, vsspeed):
|
|
"""Set vertical scan speed index"""
|
|
lib.SetVSSpeed(vsspeed)
|
|
return vsspeed
|
|
|
|
@_camfunc(getpar="channel")
|
|
def get_channel(self):
|
|
"""Get current channel index"""
|
|
@_camfunc
|
|
def get_channel_bitdepth(self, channel=None):
|
|
"""Get channel bit depth corresponding to the given channel index (current by default)"""
|
|
return lib.GetBitDepth(self._cpar["channel"] if channel is None else channel)
|
|
|
|
@_camfunc(getpar="oamp")
|
|
def get_oamp(self):
|
|
"""Get current output amplifier index"""
|
|
@_camfunc
|
|
def get_oamp_desc(self, oamp=None):
|
|
"""Get output amplifier kind corresponding to the given oamp index (current by default)"""
|
|
return py3.as_str(lib.GetAmpDesc(self._cpar["oamp"] if oamp is None else oamp))
|
|
|
|
@_camfunc(getpar="hsspeed")
|
|
def get_hsspeed(self):
|
|
"""Get current horizontal speed index"""
|
|
@_camfunc
|
|
def get_hsspeed_frequency(self, hsspeed=None):
|
|
"""Get horizontal scan frequency (in Hz) corresponding to the given hsspeed index (current by default)"""
|
|
return lib.GetHSSpeed(self._cpar["channel"],self._cpar["oamp"],self._cpar["hsspeed"] if hsspeed is None else hsspeed)*1E6
|
|
|
|
@_camfunc(getpar="preamp")
|
|
def get_preamp(self):
|
|
"""Get current preamp index"""
|
|
@_camfunc
|
|
def get_preamp_gain(self, preamp=None):
|
|
"""Get preamp gain corresponding to the given preamp index (current by default)"""
|
|
return lib.GetPreAmpGain(self._cpar["preamp"] if preamp is None else preamp)
|
|
|
|
@_camfunc(getpar="vsspeed")
|
|
def get_vsspeed(self):
|
|
"""Get current vertical speed index"""
|
|
@_camfunc
|
|
def get_vsspeed_period(self, vsspeed=None):
|
|
"""Get vertical scan period corresponding to the given vsspeed index (current by default)"""
|
|
return lib.GetVSSpeed(self._cpar["vsspeed"] if vsspeed is None else vsspeed)
|
|
|
|
@_camfunc(option=("get",AC_GETFUNC.AC_GETFUNCTION_EMCCDGAIN))
|
|
def get_EMCCD_gain(self):
|
|
"""
|
|
Get current EMCCD gain.
|
|
|
|
Return tuple ``(gain, advanced)``.
|
|
"""
|
|
return lib.get_EMCCD_gain()
|
|
@_camfunc(option=("set",AC_SETFUNC.AC_SETFUNCTION_EMCCDGAIN))
|
|
def set_EMCCD_gain(self, gain, advanced=None):
|
|
"""
|
|
Set EMCCD gain.
|
|
|
|
Gain goes up to 300 if ``advanced==False`` or higher if ``advanced==True`` (in this mode the sensor can be permanently damaged by strong light).
|
|
"""
|
|
gain=int(gain)
|
|
if (advanced is not None) and not self._check_option("set",AC_SETFUNC.AC_SETFUNCTION_EMADVANCED): return
|
|
lib.set_EMCCD_gain(gain,advanced)
|
|
|
|
def init_amp_mode(self, mode=None):
|
|
"""
|
|
Initialize the camera channel, frequencies and amp settings to some default mode.
|
|
|
|
If ``mode`` is supplied, use this mode; otherwise, use the slowest, lowest gain mode (the first one returned by :meth:`get_all_amp_modes`).
|
|
Also set the maximal recommended vertical shift speed and no EMCCD gain.
|
|
"""
|
|
mode=mode or self.get_all_amp_modes()[0]
|
|
self.set_amp_mode(mode.channel,mode.oamp,mode.hsspeed,mode.preamp)
|
|
if self.get_all_vsspeeds():
|
|
try:
|
|
vsspeed=self.get_max_vsspeed()
|
|
except AndorSDK2LibError:
|
|
vsspeed=0
|
|
self._cpar["vsspeed"]=vsspeed
|
|
self.set_vsspeed(vsspeed)
|
|
try:
|
|
self.set_EMCCD_gain(0,advanced=None)
|
|
self.set_EMCCD_gain(0,advanced=False)
|
|
except AndorNotSupportedError:
|
|
pass
|
|
|
|
### Shutter controls ###
|
|
@_camfunc(option=("feat",AC_FEATURES.AC_FEATURES_SHUTTER))
|
|
def get_min_shutter_times(self):
|
|
"""Get minimal shutter opening and closing times"""
|
|
return lib.GetShutterMinTimes() if lib.GetShutterMinTimes is not None else (0,0)
|
|
_p_shutter_mode=interface.EnumParameterClass("shutter_mode",[("auto",0),("open",1),(True,1),("closed",2),(False,2)],allowed_alias="exact")
|
|
@_camfunc(setpar="shutter",option=("feat",AC_FEATURES.AC_FEATURES_SHUTTER))
|
|
@interface.use_parameters(mode="shutter_mode",_returns=("shutter_mode",None,None,None))
|
|
def setup_shutter(self, mode, ttl_mode=0, open_time=None, close_time=None):
|
|
"""
|
|
Setup shutter.
|
|
|
|
`mode` can be ``"auto"``, ``"open"`` or ``"closed"``, ttl_mode can be 0 (low is open) or 1 (high is open),
|
|
`open_time` and `close_time` specify opening and closing times (required to calculate the minimal exposure times).
|
|
By default, these time are minimal allowed times.
|
|
"""
|
|
min_open_time,min_close_time=self.get_min_shutter_times()
|
|
open_time=min_open_time if open_time is None else open_time
|
|
close_time=min_close_time if close_time is None else close_time
|
|
lib.SetShutter(ttl_mode,mode,open_time,close_time)
|
|
return (mode,ttl_mode,open_time,close_time)
|
|
@_camfunc(getpar="shutter",defpar=(None,None,None,None))
|
|
def get_shutter_parameters(self):
|
|
"""Return shutter parameters as a tuple ``(mode, ttl_mode, open_time, close_time)``"""
|
|
def get_shutter(self):
|
|
"""Get shutter state (``"auto"``, ``"open"``, or ``"closed"``)"""
|
|
return self.get_shutter_parameters()[0]
|
|
|
|
### Misc controls ###
|
|
_p_fan_mode=interface.EnumParameterClass("fan_mode",[("full",0),("low",1),("off",2)])
|
|
@_camfunc(setpar="fan",option=("feat",AC_FEATURES.AC_FEATURES_FANCONTROL))
|
|
@interface.use_parameters(mode="fan_mode",_returns="fan_mode")
|
|
def set_fan_mode(self, mode):
|
|
"""
|
|
Set fan mode.
|
|
|
|
Can be ``"full"``, ``"low"`` or ``"off"``.
|
|
"""
|
|
lib.SetFanMode(mode)
|
|
return mode
|
|
@_camfunc(getpar="fan")
|
|
def get_fan_mode(self):
|
|
"""Return fan mode (``"full"``, ``"low"``, or ``"off"``)"""
|
|
|
|
@_camfunc
|
|
def read_in_aux_port(self, port):
|
|
"""Get state at a given auxiliary port"""
|
|
return lib.InAuxPort(port)
|
|
@_camfunc
|
|
def set_out_aux_port(self, port, state):
|
|
"""Set state at a given auxiliary port"""
|
|
return lib.OutAuxPort(port,state)
|
|
|
|
### Trigger controls ###
|
|
_trigger_desc=[
|
|
("int",0,AC_TRIGGERMODE.AC_TRIGGERMODE_INTERNAL),
|
|
("ext",1,AC_TRIGGERMODE.AC_TRIGGERMODE_EXTERNAL),
|
|
("ext_start",6,AC_TRIGGERMODE.AC_TRIGGERMODE_EXTERNALSTART),
|
|
("ext_exp",7,AC_TRIGGERMODE.AC_TRIGGERMODE_EXTERNALEXPOSURE),
|
|
("ext_fvb_em",9,AC_TRIGGERMODE.AC_TRIGGERMODE_EXTERNAL_FVB_EM),
|
|
("software",10,AC_TRIGGERMODE.AC_TRIGGERMODE_INTERNAL),
|
|
("ext_charge_shift",12,AC_TRIGGERMODE.AC_TRIGGERMODE_EXTERNAL_CHARGESHIFTING),
|
|
]
|
|
_p_trigger_mode=interface.EnumParameterClass("trigger_mode",{n:v for (n,v,_) in _trigger_desc})
|
|
_trigger_caps={v:c for (_,v,c) in _trigger_desc}
|
|
@_camfunc(setpar="trigger")
|
|
@interface.use_parameters(mode="trigger_mode",_returns="trigger_mode")
|
|
def set_trigger_mode(self, mode):
|
|
"""
|
|
Set trigger mode.
|
|
|
|
Can be ``"int"`` (internal), ``"ext"`` (external), ``"ext_start"`` (external start), ``"ext_exp"`` (external exposure),
|
|
``"ext_fvb_em"`` (external FVB EM), ``"software"`` (software trigger) or ``"ext_charge_shift"`` (external charge shifting).
|
|
|
|
For description, see Andor SDK manual.
|
|
"""
|
|
if not self._check_option("trig",self._trigger_caps[mode]): return
|
|
lib.SetTriggerMode(mode)
|
|
return mode
|
|
@_camfunc(getpar="trigger")
|
|
def get_trigger_mode(self):
|
|
"""Return trigger mode"""
|
|
@_camfunc
|
|
def get_trigger_level_limits(self):
|
|
"""Get limits on the trigger level"""
|
|
try:
|
|
return lib.GetTriggerLevelRange()
|
|
except AndorSDK2LibError as e:
|
|
if e.code==DRV_STATUS.DRV_NOT_SUPPORTED:
|
|
raise AndorNotSupportedError("setting trigger level is not supported for this camera")
|
|
else:
|
|
raise
|
|
@_camfunc(setpar="ext_trigger")
|
|
def setup_ext_trigger(self, level=None, invert=None, term_highZ=None):
|
|
"""
|
|
Setup external trigger (level, inversion, and high-Z termination).
|
|
|
|
Any ``None`` values are not changed. If any returned values are ``None``, it means that this option is not supported.
|
|
"""
|
|
if not self._has_option("trig",AC_TRIGGERMODE.AC_TRIGGERMODE_INVERTED):
|
|
invert=None
|
|
if not self._has_option("set",AC_SETFUNC.AC_SETFUNCTION_TRIGGERTERMINATION):
|
|
term_highZ=None
|
|
try:
|
|
if level is not None:
|
|
lib.SetTriggerLevel(level)
|
|
except AndorSDK2LibError as e:
|
|
if e.code==DRV_STATUS.DRV_NOT_SUPPORTED:
|
|
level=None
|
|
else:
|
|
raise
|
|
if invert is not None:
|
|
lib.SetTriggerInvert(invert)
|
|
if term_highZ is not None:
|
|
lib.SetExternalTriggerTermination(term_highZ)
|
|
curr_vals=self._cpar.get("ext_trigger",(None,None,None))
|
|
level=curr_vals[0] if level is None else level
|
|
invert=curr_vals[1] if invert is None else invert
|
|
term_highZ=curr_vals[2] if term_highZ is None else term_highZ
|
|
return (level,invert,term_highZ)
|
|
@_camfunc(getpar="ext_trigger")
|
|
def get_ext_trigger_parameters(self):
|
|
"""
|
|
Return external trigger parameters ``(level, inversion, high-Z termination)``.
|
|
|
|
If any returned values are ``None``, it means that this option is not supported.
|
|
"""
|
|
@_camfunc
|
|
def send_software_trigger(self):
|
|
"""Send software trigger signal"""
|
|
lib.SendSoftwareTrigger()
|
|
|
|
### Acquisition mode controls ###
|
|
_acqmode_desc=[
|
|
("single",1,AC_ACQMODE.AC_ACQMODE_SINGLE),
|
|
("accum",2,AC_ACQMODE.AC_ACQMODE_ACCUMULATE),
|
|
("kinetic",3,AC_ACQMODE.AC_ACQMODE_KINETIC),
|
|
("fast_kinetic",4,AC_ACQMODE.AC_ACQMODE_FASTKINETICS),
|
|
("cont",5,AC_ACQMODE.AC_ACQMODE_VIDEO),
|
|
]
|
|
_p_acq_mode=interface.EnumParameterClass("acq_mode",{n:v for (n,v,_) in _acqmode_desc})
|
|
_acqmode_caps={v:c for (_,v,c) in _acqmode_desc}
|
|
@_camfunc(setpar="acq_mode")
|
|
@interface.use_parameters(mode="acq_mode",_returns="acq_mode")
|
|
def set_acquisition_mode(self, mode, setup_params=True):
|
|
"""
|
|
Set the acquisition mode.
|
|
|
|
Can be ``"single"``, ``"accum"``, ``"kinetic"``, ``"fast_kinetic"`` or ``"cont"`` (continuous).
|
|
If ``setup_params==True``, make sure that the last specified parameters for this mode are set up.
|
|
For description of each mode, see Andor SDK manual and corresponding ``setup_*_mode`` functions.
|
|
"""
|
|
if setup_params and mode in [1,2,3,4]:
|
|
if mode==1:
|
|
self.setup_accum_mode(*self._cpar["acq_params/accum"])
|
|
elif mode==2:
|
|
self.setup_kinetic_mode(*self._cpar["acq_params/kinetic"])
|
|
elif mode==3:
|
|
self.setup_fast_kinetic_mode(*self._cpar["acq_params/fast_kinetic"])
|
|
elif mode==4:
|
|
self.setup_cont_mode(*self._cpar["acq_params/cont"])
|
|
else:
|
|
if not self._check_option("acq",self._acqmode_caps[mode]): return
|
|
lib.SetAcquisitionMode(mode)
|
|
return mode
|
|
@_camfunc(getpar="acq_mode")
|
|
def get_acquisition_mode(self):
|
|
"""Get the current acquisition mode"""
|
|
|
|
@_camfunc(setpar="acq_params/accum",option=("acq",AC_ACQMODE.AC_ACQMODE_ACCUMULATE))
|
|
def setup_accum_mode(self, num_acc, cycle_time_acc=0):
|
|
"""
|
|
Switch into the accum acquisition mode and set up its parameters.
|
|
|
|
`num_acc` is the number of accumulated frames, `cycle_time_acc` is the acquisition period
|
|
(by default the minimal possible based on exposure and transfer time).
|
|
"""
|
|
if self.set_acquisition_mode("accum",setup_params=False) is None: return
|
|
lib.SetNumberAccumulations(num_acc)
|
|
lib.SetAccumulationCycleTime(cycle_time_acc)
|
|
return (num_acc,cycle_time_acc)
|
|
@_camfunc(getpar="acq_params/accum")
|
|
def get_accum_mode_parameters(self):
|
|
"""Return accum acquisition mode parameters ``(num_acc, cycle_time_acc)``"""
|
|
|
|
@_camfunc(setpar="acq_params/kinetic",option=("acq",AC_ACQMODE.AC_ACQMODE_KINETIC))
|
|
def setup_kinetic_mode(self, num_cycle, cycle_time=0., num_acc=1, cycle_time_acc=0, num_prescan=0):
|
|
"""
|
|
Switch into the kinetic acquisition mode and set up its parameters.
|
|
|
|
`num_cycle` is the number of kinetic cycles frames, `cycle_time` is the acquisition period between accum frames,
|
|
`num_accum` is the number of accumulated frames, `cycle_time_acc` is the accum acquisition period,
|
|
`num_prescan` is the number of prescans.
|
|
"""
|
|
if self.set_acquisition_mode("kinetic",setup_params=False) is None: return
|
|
lib.SetNumberKinetics(num_cycle)
|
|
lib.SetNumberAccumulations(num_acc)
|
|
if self._has_option("set",AC_SETFUNC.AC_SETFUNCTION_PRESCANS):
|
|
lib.SetNumberPrescans(num_prescan)
|
|
else:
|
|
num_prescan=0
|
|
lib.SetKineticCycleTime(cycle_time)
|
|
lib.SetAccumulationCycleTime(cycle_time_acc)
|
|
return (num_cycle,cycle_time,num_acc,cycle_time_acc,num_prescan)
|
|
@_camfunc(getpar="acq_params/kinetic")
|
|
def get_kinetic_mode_parameters(self):
|
|
"""Return kinetic acquisition mode parameters ``(num_cycle, cycle_time, num_acc, cycle_time_acc, num_prescan)``"""
|
|
|
|
@_camfunc(setpar="acq_params/fast_kinetic",option=("acq",AC_ACQMODE.AC_ACQMODE_FASTKINETICS))
|
|
def setup_fast_kinetic_mode(self, num_acc, cycle_time_acc=0.):
|
|
"""
|
|
Switch into the fast kinetic acquisition mode and set up its parameters.
|
|
|
|
`num_acc` is the number of accumulated frames, `cycle_time_acc` is the acquisition period
|
|
(by default the minimal possible based on exposure and transfer time).
|
|
"""
|
|
if self.set_acquisition_mode("fast_kinetic",setup_params=False) is None: return
|
|
lib.SetNumberKinetics(num_acc)
|
|
lib.SetAccumulationCycleTime(cycle_time_acc)
|
|
return (num_acc,cycle_time_acc)
|
|
@_camfunc(getpar="acq_params/fast_kinetic")
|
|
def get_fast_kinetic_mode_parameters(self):
|
|
"""Return fast kinetic acquisition mode parameters ``(num_acc, cycle_time_acc)``"""
|
|
|
|
@_camfunc(setpar="acq_params/cont",option=("acq",AC_ACQMODE.AC_ACQMODE_VIDEO))
|
|
def setup_cont_mode(self, cycle_time=0):
|
|
"""
|
|
Switch into the continuous acquisition mode and set up its parameters.
|
|
|
|
`cycle_time` is the acquisition period (by default the minimal possible based on exposure and transfer time).
|
|
"""
|
|
if self.set_acquisition_mode("cont",setup_params=False) is None: return
|
|
lib.SetKineticCycleTime(cycle_time)
|
|
return cycle_time
|
|
@_camfunc(getpar="acq_params/cont")
|
|
def get_cont_mode_parameters(self):
|
|
"""Return continuous acquisition mode parameters ``cycle_time``"""
|
|
|
|
@_camfunc
|
|
def set_exposure(self, exposure):
|
|
"""Set camera exposure"""
|
|
lib.SetExposureTime(exposure)
|
|
return self.get_exposure()
|
|
@_camfunc
|
|
def get_exposure(self):
|
|
"""Get current exposure"""
|
|
return self.get_frame_timings()[0]
|
|
def set_frame_period(self, frame_period):
|
|
"""Set frame acquisition period for the continuous mode"""
|
|
self.setup_cont_mode(frame_period)
|
|
@_camfunc(setpar="frame_transfer",option=("acq",AC_ACQMODE.AC_ACQMODE_FRAMETRANSFER))
|
|
def enable_frame_transfer_mode(self, enable=True):
|
|
"""
|
|
Enable frame transfer mode.
|
|
|
|
For description, see Andor SDK manual.
|
|
"""
|
|
lib.SetFrameTransferMode(enable)
|
|
return enable
|
|
@_camfunc(getpar="frame_transfer")
|
|
def is_frame_transfer_enabled(self):
|
|
"""Return whether the frame transfer mode is enabled"""
|
|
|
|
@_camfunc
|
|
def get_cycle_timings(self):
|
|
"""
|
|
Get acquisition timing.
|
|
|
|
Return tuple ``(exposure, accum_cycle_time, kinetic_cycle_time)``.
|
|
In continuous mode, the relevant cycle time is ``kinetic_cycle_time``.
|
|
"""
|
|
return TCycleTimings(*lib.GetAcquisitionTimings())
|
|
@_camfunc
|
|
def get_frame_timings(self):
|
|
"""
|
|
Get acquisition timing.
|
|
|
|
Return tuple ``(exposure, frame_period)``.
|
|
Frame period is the rate of frame generation, not of internal frame acquisition
|
|
(e.g., in accumulator or kinetic mode this is the rate of generating a single accumulated frame, which is ``num_acc`` times larger than the internal frame period).
|
|
"""
|
|
cycle_timings=self.get_cycle_timings()
|
|
return self._TAcqTimings(cycle_timings.exposure,cycle_timings.kinetic_cycle_time)
|
|
@_camfunc
|
|
def get_readout_time(self):
|
|
"""Get frame readout time"""
|
|
try:
|
|
return lib.GetReadOutTime()
|
|
except AndorSDK2LibError as err:
|
|
if err.code==DRV_STATUS.DRV_ERROR_CODES: # occasional Andor Luca error
|
|
return None
|
|
raise
|
|
@_camfunc
|
|
def get_keepclean_time(self):
|
|
"""Get sensor keep-clean time"""
|
|
return lib.GetKeepCleanTime()
|
|
|
|
|
|
### Image settings and transfer controls ###
|
|
_readmode_desc=[
|
|
("fvb",0,AC_READMODE.AC_READMODE_FVB),
|
|
("multi_track",1,AC_READMODE.AC_READMODE_MULTITRACK),
|
|
("random_track",2,AC_READMODE.AC_READMODE_RANDOMTRACK),
|
|
("single_track",3,AC_READMODE.AC_READMODE_SINGLETRACK),
|
|
("image",4,AC_READMODE.AC_READMODE_FULLIMAGE),
|
|
]
|
|
_p_read_mode=interface.EnumParameterClass("read_mode",{n:v for (n,v,_) in _readmode_desc})
|
|
_readmode_caps={v:c for (_,v,c) in _readmode_desc}
|
|
@camera.acqstopped
|
|
@_camfunc(setpar="read_mode")
|
|
@interface.use_parameters(mode="read_mode",_returns="read_mode")
|
|
def set_read_mode(self, mode):
|
|
"""
|
|
Set camera read mode.
|
|
|
|
Can be ``"fvb"`` (average all image vertically and return it as one row), ``"single_track"`` (read a single row or several rows averaged together),
|
|
``"multi_track"`` (read multiple rows or averaged sets of rows), ``"random_track"`` (read several arbitrary lines),
|
|
or ``"image"`` (read a whole image or its rectangular part).
|
|
"""
|
|
if not self._check_option("read",self._readmode_caps[mode]): return
|
|
lib.SetReadMode(mode)
|
|
return mode
|
|
@_camfunc(getpar="read_mode")
|
|
def get_read_mode(self):
|
|
"""Get the current read mode"""
|
|
|
|
@camera.acqstopped
|
|
@_camfunc(setpar="read_params/single_track")
|
|
def setup_single_track_mode(self, center=0, width=1):
|
|
"""
|
|
Switch into the singe-track read mode and set up its parameters.
|
|
|
|
`center` and `width` specify selection of the rows to be averaged together.
|
|
"""
|
|
if self.set_read_mode("single_track") is None: return
|
|
lib.SetSingleTrack(center+1,width)
|
|
return (center,width)
|
|
@_camfunc(getpar="read_params/single_track")
|
|
def get_single_track_mode_parameters(self):
|
|
"""Return singe-track read mode parameters ``(center, width)``"""
|
|
|
|
@camera.acqstopped
|
|
@_camfunc
|
|
def setup_multi_track_mode(self, number=1, height=1, offset=0):
|
|
"""
|
|
Switch into the multi-track read mode and set up its parameters.
|
|
|
|
`number` is the number of rows (or row sets) to read, `height` is number of one row set (1 for a single row),
|
|
`offset` is the distance between the row sets.
|
|
Return a tuple ``(number, height, offset, top, gap)``, where ``top`` is the offset of the first row from the top, and ``gap`` is the gap between the tracks.
|
|
"""
|
|
if self.set_read_mode("multi_track") is None: return
|
|
res=lib.SetMultiTrack(number,height,offset)
|
|
self._cpar["read_params/multi_track"]=(number,height,offset)
|
|
return (number,height,offset)+res
|
|
@_camfunc(getpar="read_params/multi_track")
|
|
def get_multi_track_mode_parameters(self):
|
|
"""Return multi-track read mode parameters ``(number, height, offset)``"""
|
|
|
|
@camera.acqstopped
|
|
@_camfunc(setpar="read_params/random_track")
|
|
def setup_random_track_mode(self, tracks=None):
|
|
"""
|
|
Switch into the random-track read mode and set up its parameters.
|
|
|
|
`tracks` is a list of tuples ``(start, stop)`` specifying track span (start are inclusive, stop are exclusive, starting from 0).
|
|
Note that it does not affect the current read mode, which should be set using :meth:`set_read_mode`.
|
|
"""
|
|
if self.set_read_mode("random_track") is None: return
|
|
tracks=tracks or [(0,1)]
|
|
lib.SetRandomTracks([(t[0]+1,t[1]) for t in tracks])
|
|
return list(tracks)
|
|
@_camfunc(getpar="read_params/random_track")
|
|
def get_random_track_mode_parameters(self):
|
|
"""Return random-track read mode parameters, i.e., the list of track positions"""
|
|
|
|
@camera.acqstopped
|
|
@_camfunc(setpar="read_params/image")
|
|
def setup_image_mode(self, hstart=0, hend=None, vstart=0, vend=None, hbin=1, vbin=1):
|
|
"""
|
|
Switch into the image read mode and set up its parameters.
|
|
|
|
`hstart` and `hend` specify horizontal image extent, `vstart` and `vend` specify vertical image extent
|
|
(start are inclusive, stop are exclusive, starting from 0), `hbin` and `vbin` specify binning.
|
|
By default, all non-supplied parameters take extreme values.
|
|
"""
|
|
if self.set_read_mode("image") is None: return
|
|
hbin=self._truncate_roi_binning(hbin)
|
|
vbin=self._truncate_roi_binning(vbin)
|
|
hlim,vlim=self.get_roi_limits(hbin=hbin,vbin=vbin)
|
|
hstart,hend,hbin=self._truncate_roi_axis((hstart,hend,hbin),hlim)
|
|
vstart,vend,vbin=self._truncate_roi_axis((vstart,vend,vbin),vlim)
|
|
try:
|
|
lib.SetImage(hbin,vbin,hstart+1,hend,vstart+1,vend)
|
|
except AndorSDK2LibError:
|
|
hbin=vbin=1
|
|
lib.SetImage(hbin,vbin,hstart+1,hend,vstart+1,vend)
|
|
return (hstart,hend,vstart,vend,hbin,vbin)
|
|
@_camfunc(getpar="read_params/image")
|
|
def get_image_mode_parameters(self):
|
|
"""Return image read mode parameters, ``(hstart, hend, vstart, vend, hbin, vbin)``"""
|
|
|
|
@_camfunc(option=("get",AC_GETFUNC.AC_GETFUNCTION_DETECTORSIZE))
|
|
def get_detector_size(self):
|
|
return lib.GetDetector()
|
|
def get_roi(self):
|
|
return self.get_image_mode_parameters()
|
|
def set_roi(self, hstart=0, hend=None, vstart=0, vend=None, hbin=1, vbin=1):
|
|
return self.setup_image_mode(hstart,hend,vstart,vend,hbin,vbin)
|
|
|
|
def _find_min_roi_end(self, kind="h"):
|
|
wdet,hdet=self.get_detector_size()
|
|
if self.set_read_mode("image") is None:
|
|
return
|
|
smin,smax=1,(wdet if kind=="h" else hdet)
|
|
if not self._check_option("read",AC_READMODE.AC_READMODE_SUBIMAGE):
|
|
return smax
|
|
while smin<smax-1:
|
|
smid=(smin+smax)//2
|
|
try:
|
|
if kind=="h":
|
|
lib.SetImage(1,1,1,smid,1,hdet)
|
|
else:
|
|
lib.SetImage(1,1,1,wdet,1,smid)
|
|
smax=smid
|
|
except AndorSDK2LibError:
|
|
smin=smid
|
|
lib.SetImage(1,1,1,wdet,1,hdet)
|
|
return smax
|
|
def _truncate_roi_binning(self, binv):
|
|
wdet,hdet=self.get_detector_size()
|
|
maxbin=int(min(wdet//self._minh,hdet//self._minv,32)) if (self._minh and self._minv) else 32
|
|
return max(1,min(binv,maxbin))
|
|
def get_roi_limits(self, hbin=1, vbin=1):
|
|
wdet,hdet=self.get_detector_size()
|
|
maxbin=int(min(wdet//self._minh,hdet//self._minv,32)) if (self._minh and self._minv) else 32
|
|
hlim=camera.TAxisROILimit(self._minh*hbin,wdet,1,hbin,maxbin)
|
|
vlim=camera.TAxisROILimit(self._minv*vbin,hdet,1,vbin,maxbin)
|
|
return hlim,vlim
|
|
|
|
def _get_data_dimensions_rc(self):
|
|
mode=self.get_read_mode()
|
|
params=self._cpar.get("read_params/"+mode,None)
|
|
hdet,_=self.get_detector_size()
|
|
if mode in {"fvb","single_track"}:
|
|
return (1,hdet)
|
|
if mode=="multi_track":
|
|
return (params[0],hdet)
|
|
if mode=="random_track":
|
|
return (len(params),hdet)
|
|
if mode=="image":
|
|
(hstart,hend,vstart,vend,hbin,vbin)=params
|
|
return (vend-vstart)//vbin,(hend-hstart)//hbin
|
|
|
|
### Acquisition process controls ###
|
|
@_camfunc
|
|
def setup_acquisition(self, mode=None, nframes=None): # pylint: disable=arguments-differ
|
|
mode={"snap":"kinetic","sequence":"cont"}.get(mode,mode)
|
|
if mode=="kinetic" and nframes is not None:
|
|
par=self.get_kinetic_mode_parameters()
|
|
self.setup_kinetic_mode(nframes,*par[1:])
|
|
elif mode is not None:
|
|
self.set_acquisition_mode(mode)
|
|
else:
|
|
mode=self.get_acquisition_mode()
|
|
lib.PrepareAcquisition()
|
|
super().setup_acquisition(mode=mode)
|
|
@_camfunc
|
|
def clear_acquisition(self):
|
|
self.stop_acquisition()
|
|
lib.FreeInternalMemory()
|
|
super().clear_acquisition()
|
|
@_camfunc
|
|
def start_acquisition(self, *args, **kwargs):
|
|
self.stop_acquisition()
|
|
super().start_acquisition(*args,**kwargs)
|
|
self._frame_counter.reset(int(self.get_buffer_size()*0.9)) # keep some buffer excess to account fro frames lost while reading / between request and reading
|
|
lib.StartAcquisition()
|
|
@_camfunc
|
|
def stop_acquisition(self):
|
|
if self.acquisition_in_progress():
|
|
self._frame_counter.update_acquired_frames(self._get_acquired_frames())
|
|
lib.AbortAcquisition()
|
|
|
|
@_camfunc
|
|
def get_acquisition_progress(self):
|
|
"""
|
|
Get acquisition progress.
|
|
|
|
Return tuple ``(frames_done, acc_done)`` with the number of full transferred frames
|
|
and the number of acquired sub-frames in the current accumulation cycle.
|
|
"""
|
|
acc_done,frames_done=lib.GetAcquisitionProgress()
|
|
return TAcqProgress(frames_done,acc_done)
|
|
def _get_acquired_frames(self):
|
|
return self.get_acquisition_progress().frames_done
|
|
|
|
@_camfunc
|
|
def get_buffer_size(self):
|
|
"""Get the size of the image ring buffer"""
|
|
try:
|
|
self._buffer_size=lib.GetSizeOfCircularBuffer()
|
|
except AndorSDK2LibError:
|
|
pass
|
|
return self._buffer_size
|
|
|
|
@_camfunc
|
|
def _wait_for_next_frame(self, timeout=20., idx=None):
|
|
if timeout is None or timeout>0.1:
|
|
timeout=0.1
|
|
try:
|
|
try:
|
|
_camsel_lock.release()
|
|
lib.WaitForAcquisitionByHandleTimeOut(self.handle,int(timeout*1E3))
|
|
finally:
|
|
_camsel_lock.acquire()
|
|
except AndorSDK2LibError as e:
|
|
if e.code!=DRV_STATUS.DRV_NO_NEW_DATA:
|
|
raise
|
|
@_camfunc
|
|
def _read_frames(self, rng, return_info=False):
|
|
"""
|
|
Read and return frames given the range.
|
|
|
|
The range is always a tuple with at least a single frame in it, and is guaranteed to already be valid.
|
|
Always return tuple ``(frames, infos)``; if ``return_info==False``, ``infos`` value is ignored, so it can be anything (e.g., ``None``).
|
|
"""
|
|
dim=self._get_data_dimensions_rc()
|
|
dt=np.dtype(self._default_image_dtype)
|
|
get_method=lib.GetImages16 if dt.itemsize<=2 else lib.GetImages
|
|
data,_,_=get_method(rng[0]+1,rng[1],dim[0]*dim[1]*(rng[1]-rng[0]))
|
|
data=self._convert_indexing(data.reshape((-1,dim[0],dim[1])),"rcb",axes=(1,2))
|
|
return list(data),None
|
|
|
|
def _get_grab_acquisition_parameters(self, nframes, buff_size):
|
|
return {"mode":"cont"} |