From da5a09b1541677ada5174d05135683fc5f106454 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Fri, 4 Aug 2017 12:54:03 +0200 Subject: [PATCH 01/27] Added WFS abstract class --- microscope/devices.py | 213 ++++++++++++++++++++++- microscope/wavefront_sensors/__init__.py | 0 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 microscope/wavefront_sensors/__init__.py diff --git a/microscope/devices.py b/microscope/devices.py index df3647d6..72e35b4d 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -46,7 +46,7 @@ (TRIGGER_AFTER, TRIGGER_BEFORE, TRIGGER_DURATION, TRIGGER_SOFT) = range(4) # Device types. -(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER) = range(6) +(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER, UWAVEFRONTSENSOR) = range(7) # Mapping of setting data types to descriptors allowed-value description types. # For python 2 and 3 compatibility, we convert the type into a descriptor string. @@ -766,3 +766,214 @@ def set_power_mw(self, mw): self._set_point = mw self._set_power_mw(mw) + +# === WavefrontSensorDevice === +class WavefrontSensorDevice(CameraDevice): + __metaclass__ = abc.ABCMeta + """Adds functionality to DataDevice to support WavefrontSensors (WFS). + As, in practice, all wavefront sensors are based on a camera, + I think it makes sense here to subclassing a CameraDevice + + Defines the interface for WFSs. + Applies transformations to the acquired data to get the relevant information: + intensity and wavefront maps, Zernike polynomials,... + """ + def __init__(self, *args, **kwargs): + super(WavefrontSensorDevice, self).__init__(**kwargs) + # Add WFS settings + # Zernike indexes to be returned + self._zernike_indexes = [] + self.add_setting('zernike_indexes', 'int', + self.get_zernike_indexes, + self.set_zernike_indexes, + 'list of integers')) + # Filter the phase map returned by a number of the Zernike measurements + self._zernike_phase_filter = [] + self.add_setting('zernike_phase_filter', 'bool', + self.get_zernike_phase_filter, + self.set_zernike_phase_filter, + 'list of integers') + # WaveLength at which the phase map is calculated + self._wavelength = None + self.add_setting('wavelength', 'int', + self.get_wavelength, + self.set_wavelength, + 'wavelength in nm') + + # Some acquisition related methods # + + @abc.abstractmethod + def _process_data(self, data): + """Apply necessary transformations to data to be served to the client. + + Return as a dictionary: + - intensity_map: a 2D array containing the intensity + - linearity: some measure of the linearity of the data. + Simple saturation at the intensity map might not be enough to indicate + if we are exposing correctly to get a accurate measure of the phase. + - phase_map: a 2D array containing the phase + - tilts: a tuple containing X and Y tilts + - RMS: the root mean square measurement + - PtoV: peak to valley measurement + - zernike_polynomials: a list with the relevant Zernike polynomials + """ + + flips = (self._transform[0], self._transform[1]) + rot = self._transform[2] + + # Choose appropriate transform based on (flips, rot). + return {(0, 0): numpy.rot90(data, rot), + (0, 1): numpy.flipud(numpy.rot90(data, rot)), + (1, 0): numpy.fliplr(numpy.rot90(data, rot)), + (1, 1): numpy.fliplr(numpy.flipud(numpy.rot90(data, rot))) + }[flips] + + # Some measurements related methods + # We made the distinction here between ROI (form CameraDevice superclass) + # and pupil. + # ROI is the area that is acquired and shown in the intensity images. + # The pupil is the area that is analyzed for measuring phase. + + @abc.abstractmethod + def _get_pupil(self): + """Return the pupil that is measured. + Pupil on WFS are not per se rectangular neither circular. + We might define them as: + 1- a mask: a binary 2D array of the size of the sensor + 2- as a list of sub-pupils each containing: + - the size (left, top, width, height) of a surrounding or enclosed rectangle + - whether the rectangle is surrounding or enclosed + - the contained shape: circular/oval or rectangular + While in most cases the pupil can be defined by a simple circle, it is + convenient to add the option of a mask. + Pupil has to be returned as a numpy array with the same orientation as the ROI + as it is going to use the same transform methods + """ + pass + + @Pyro4.expose + def get_pupil(self): + """Return pupil in a convenient way for the client.""" + pupil = self._get_pupil() + # TODO: depending on the format of the pupil, we might have to transform it + # if self._transform[2]: + # # 90 degree rotation + # pupil = numpy.rot90(pupil) + return pupil + + @abc.abstractmethod + def _set_pupil(self): + """Set the pupil on the hardware, return True if successful.""" + return False + + @Pyro4.expose + def set_pupil(self, pupil): + """Set the pupil according to the provided parameters. + + Return True if pupil set correctly, False otherwise.""" + # TODO: depending on the format of the pupil, we might have to transform it + # if self._transform[2]: + # roi = (top, left, height, width) + # else: + # roi = (left, top, width, height) + return self._set_pupil(pupil) + + @abc.abstractmethod + def _set_reference(self, source): + """Reference the WFS to either: + - the manufacturer's reference + - a reference from a grabbed measurement + - a reference from a file + + :return: True if successful + """ + pass + + @Pyro4.expose + def set_reference(self, source): + """Reference the WFS to either: + - the manufacturer's reference + - a reference from a grabbed measurement + - a reference from a file + for all the phase measurements + """ + self._set_reference(source) + + @abc.abstractmethod + def _get_reference(self): + """Get the reference of the WFS""" + pass + + @Pyro4.expose + def get_reference(self): + """Return to what the WFS is currently referenced to.""" + return self._get_reference() + + @Pyro4.expose + @abc.abstractmethod + def acquire_dark_image(self): + """Acquire a dark image on the camera""" + pass + + @Pyro4.expose + def get_zernike_indexes(self): + """Returns a list with the Zernike indexes that are returned in + a measurement""" + return self._zernike_indexes + + @abc.abstractmethod + def _set_zernike_indexes(self, indexes): + """Set the indexes of the polynomials that have to be returned by the SDK or the hardware. + + :param indexes: a list of integers representing the indexes to return + :return True if success + """ + pass + + @Pyro4.expose + def set_zernike_indexes(self, indexes): + """Set the indexes of the Zernike polynomials + that have to be returned by the WFS""" + if self._set_zernike_indexes(indexes): + self._zernike_indexes = indexes + + @Pyro4.expose + def get_zernike_phase_filter(self): + return self._zernike_phase_filter + + @abc.abstractmethod + def _set_zernike_phase_filter(self, phase_filter): + """Set a filter of Zernike polynomials by which the phase map has to be filtered. + + :param phase_filter: a list of integers representing the indexes to filter. [] no filtering + :return True if success + """ + pass + + @Pyro4.expose + def set_zernike_phase_filter(self, phase_filter): + """Set a filter of Zernike polynomials by which the phase map has to be filtered. + + :param phase_filter: a list of integers representing the indexes to filter. [] no filtering + """ + if self._set_zernike_phase_filter(phase_filter): + self._zernike_phase_filter = phase_filter + + @Pyro4.expose + def get_wavelength(self): + return self._wavelength + + @abc.abstractmethod + def _set_wavelength(self, wavelength): + """Sets the wavelength that is used for tha phase calculation + + :param wavelength: integer representing the wavelength in nm. + :return True if success + """ + pass + + @Pyro4.expose + def set_wavelength(self, wavelength): + if self._set_wavelength(wavelength): + self._wavelength = wavelength + diff --git a/microscope/wavefront_sensors/__init__.py b/microscope/wavefront_sensors/__init__.py new file mode 100644 index 00000000..e69de29b From 0442da7e65896aaa386b21ad07226495dbf7e2f2 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Mon, 7 Aug 2017 17:07:19 +0200 Subject: [PATCH 02/27] Added Some SID4 SDK methods --- microscope/wavefront_sensors/AndorSDK3.py | 297 ++++++ .../wavefront_sensors/AndorSDK3Camera.py | 160 +++ microscope/wavefront_sensors/AndorZyla.py | 970 ++++++++++++++++++ microscope/wavefront_sensors/ConsoleTests.py | 244 +++++ microscope/wavefront_sensors/SID4_SDK.py | 388 +++++++ .../SID4_SDK_ChangeMask_Example.cpp | 203 ++++ .../SID4_SDK_ChangeMask_Example2.cpp | 260 +++++ .../SID4_SDK_FileAnalysis_Example.cpp | 109 ++ .../SID4_SDK_GrabImage_Example.cpp | 104 ++ .../SID4_SDK_OpenSID4_Example.cpp | 31 + .../SID4_SDK_SaveMeasurement_Example.cpp | 137 +++ microscope/wavefront_sensors/camera.py | 577 +++++++++++ microscope/wavefront_sensors/memoryHandler.py | 185 ++++ 13 files changed, 3665 insertions(+) create mode 100644 microscope/wavefront_sensors/AndorSDK3.py create mode 100644 microscope/wavefront_sensors/AndorSDK3Camera.py create mode 100644 microscope/wavefront_sensors/AndorZyla.py create mode 100644 microscope/wavefront_sensors/ConsoleTests.py create mode 100644 microscope/wavefront_sensors/SID4_SDK.py create mode 100644 microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp create mode 100644 microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example2.cpp create mode 100644 microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp create mode 100644 microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp create mode 100644 microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp create mode 100644 microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp create mode 100644 microscope/wavefront_sensors/camera.py create mode 100644 microscope/wavefront_sensors/memoryHandler.py diff --git a/microscope/wavefront_sensors/AndorSDK3.py b/microscope/wavefront_sensors/AndorSDK3.py new file mode 100644 index 00000000..e38cb8a6 --- /dev/null +++ b/microscope/wavefront_sensors/AndorSDK3.py @@ -0,0 +1,297 @@ +#!/usr/bin/python + +################## +# AndorCam.py +# +# Copyright David Baddeley, 2009 +# d.baddeley@auckland.ac.nz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################## + +import ctypes +import platform + +_stdcall_libraries = {} + +arch, plat = platform.architecture() + +#if 'WinDLL' in dir(): +if plat.startswith('Windows'): + if arch == '32bit': + _stdcall_libraries['ATCORE'] = ctypes.WinDLL('C:/Program Files/Andor SDK3/atcore') + _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('C:/Program Files/Andor SDK3/atutility') + else: + _stdcall_libraries['ATCORE'] = ctypes.OleDLL('C:/Program Files/Andor SDK3/atcore') + _stdcall_libraries['ATUTIL'] = ctypes.OleDLL('C:/Program Files/Andor SDK3/atutility') +else: + _stdcall_libraries['ATCORE'] = ctypes.CDLL('libatcore.so') + _stdcall_libraries['ATUTIL'] = ctypes.CDLL('libatutility.so') + +#### typedefs +AT_H = ctypes.c_int +AT_BOOL = ctypes.c_int +AT_64 = ctypes.c_int64 +AT_U8 = ctypes.c_uint8 +AT_WC = ctypes.c_wchar + +from ctypes import POINTER, c_int, c_uint, c_double + +#### Defines +errorCodes = {} +def errCode(name,value): + errorCodes[value] = name + +AT_INFINITE = 0xFFFFFFFF +AT_CALLBACK_SUCCESS = 0 + +AT_TRUE = 1 +AT_FALSE = 0 + +AT_SUCCESS = 0 +errCode('AT_ERR_NOTINITIALISED', 1) +errCode('AT_ERR_NOTIMPLEMENTED', 2) +errCode('AT_ERR_READONLY', 3) +errCode('AT_ERR_NOTREADABLE', 4) +errCode('AT_ERR_NOTWRITABLE', 5) +errCode('AT_ERR_OUTOFRANGE', 6) +errCode('AT_ERR_INDEXNOTAVAILABLE', 7) +errCode('AT_ERR_INDEXNOTIMPLEMENTED', 8) +errCode('AT_ERR_EXCEEDEDMAXSTRINGLENGTH', 9) +errCode('AT_ERR_CONNECTION', 10) +errCode('AT_ERR_NODATA', 11) +errCode('AT_ERR_INVALIDHANDLE', 12) +errCode('AT_ERR_TIMEDOUT', 13) +errCode('AT_ERR_BUFFERFULL', 14) +errCode('AT_ERR_INVALIDSIZE', 15) +errCode('AT_ERR_INVALIDALIGNMENT', 16) +errCode('AT_ERR_COMM', 17) +errCode('AT_ERR_STRINGNOTAVAILABLE', 18) +errCode('AT_ERR_STRINGNOTIMPLEMENTED', 19) + +errCode('AT_ERR_NULL_FEATURE', 20) +errCode('AT_ERR_NULL_HANDLE', 21) +errCode('AT_ERR_NULL_IMPLEMENTED_VAR', 22) +errCode('AT_ERR_NULL_READABLE_VAR', 23) +errCode('AT_ERR_NULL_READONLY_VAR', 24) +errCode('AT_ERR_NULL_WRITABLE_VAR', 25) +errCode('AT_ERR_NULL_MINVALUE', 26) +errCode('AT_ERR_NULL_MAXVALUE', 27) +errCode('AT_ERR_NULL_VALUE', 28) +errCode('AT_ERR_NULL_STRING', 29) +errCode('AT_ERR_NULL_COUNT_VAR', 30) +errCode('AT_ERR_NULL_ISAVAILABLE_VAR', 31) +errCode('AT_ERR_NULL_MAXSTRINGLENGTH', 32) +errCode('AT_ERR_NULL_EVCALLBACK', 33) +errCode('AT_ERR_NULL_QUEUE_PTR', 34) +errCode('AT_ERR_NULL_WAIT_PTR', 35) +errCode('AT_ERR_NULL_PTRSIZE', 36) +errCode('AT_ERR_NOMEMORY', 37) +errCode('AT_ERR_DEVICEINUSE', 38) + +errCode('AT_ERR_HARDWARE_OVERFLOW', 100) + +class CameraError(Exception): + def __init__(self, fcnName, errNo): + self.errNo = errNo + self.fcnName = fcnName + + def __str__(self): + return 'when calling %s - %s' % (self.fcnName, errorCodes[self.errNo]) + + +#special case for buffer timeout +AT_ERR_TIMEDOUT = 13 +AT_ERR_NODATA = 11 + +class TimeoutError(CameraError): + pass + + + +AT_HANDLE_UNINITIALISED = -1 +AT_HANDLE_SYSTEM = 1 + +### Functions ### +STRING = POINTER(AT_WC) + +#classes so that we do some magic and automatically add byrefs etc ... can classify outputs +class _meta(object): + pass + +class OUTPUT(_meta): + def __init__(self, val): + self.type = val + self.val = POINTER(val) + + def getVar(self, bufLen=0): + v = self.type() + return v, ctypes.byref(v) + +class _OUTSTRING(OUTPUT): + def __init__(self): + self.val = STRING + + def getVar(self, bufLen): + v = ctypes.create_unicode_buffer(bufLen) + return v, v + +OUTSTRING = _OUTSTRING() + +class _OUTSTRLEN(_meta): + def __init__(self): + self.val = c_int + +OUTSTRLEN = _OUTSTRLEN() + + +def stripMeta(val): + if isinstance(val, _meta): + return val.val + else: + return val + +class dllFunction(object): + def __init__(self, name, args = [], argnames = [], lib='ATCORE'): + self.f = getattr(_stdcall_libraries[lib], name) + self.f.restype = c_int + self.f.argtypes = [stripMeta(a) for a in args] + + self.fargs = args + self.fargnames = argnames + self.name = name + + self.inp = [not isinstance(a, OUTPUT) for a in args] + self.in_args = [a for a in args if not isinstance(a, OUTPUT)] + self.out_args = [a for a in args if isinstance(a, OUTPUT)] + + self.buf_size_arg_pos = -1 + for i in range(len(self.in_args)): + if isinstance(self.in_args[i], _OUTSTRLEN): + self.buf_size_arg_pos = i + + ds = name + '\n\nArguments:\n===========\n' + for i in range(len(args)): + an = '' + if i = 0: + bs = args[self.buf_size_arg_pos] + else: + bs = 255 + + for j in range(len(self.inp)): + if self.inp[j]: #an input + ars.append(args[i]) + i+=1 + else: #an output + r, ar = self.fargs[j].getVar(bs) + ars.append(ar) + ret.append(r) + #print r, r._type_ + + res = self.f(*ars) + + if not res == AT_SUCCESS: + if res == AT_ERR_TIMEDOUT or res == AT_ERR_NODATA: + #handle timeouts as a special case, as we expect to get them + raise TimeoutError(self.name, res) + else: + print ars + print res + raise CameraError(self.name, res) + + if len(ret) == 0: + return None + if len(ret) == 1: + return ret[0] + else: + return ret + + + + +def dllFunc(name, args = [], argnames = [], lib='ATCORE'): + f = dllFunction(name, args, argnames, lib) + globals()[name[3:]] = f + +dllFunc('AT_InitialiseLibrary') +dllFunc('AT_FinaliseLibrary') + +dllFunc('AT_Open', [c_int, OUTPUT(AT_H)]) +dllFunc('AT_Close', [AT_H]) + +dllFunc('AT_IsImplemented', [AT_H, STRING, OUTPUT(AT_BOOL)]) +dllFunc('AT_IsReadable', [AT_H, STRING, OUTPUT(AT_BOOL)]) +dllFunc('AT_IsWritable', [AT_H, STRING, OUTPUT(AT_BOOL)]) +dllFunc('AT_IsReadOnly', [AT_H, STRING, OUTPUT(AT_BOOL)]) + +dllFunc('AT_SetInt', [AT_H, STRING, AT_64]) +dllFunc('AT_GetInt', [AT_H, STRING, OUTPUT(AT_64)]) +dllFunc('AT_GetIntMax', [AT_H, STRING, OUTPUT(AT_64)]) +dllFunc('AT_GetIntMin', [AT_H, STRING, OUTPUT(AT_64)]) + +dllFunc('AT_SetFloat', [AT_H, STRING, c_double]) +dllFunc('AT_GetFloat', [AT_H, STRING, OUTPUT(c_double)]) +dllFunc('AT_GetFloatMax', [AT_H, STRING, OUTPUT(c_double)]) +dllFunc('AT_GetFloatMin', [AT_H, STRING, OUTPUT(c_double)]) + +dllFunc('AT_SetBool', [AT_H, STRING, AT_BOOL]) +dllFunc('AT_GetBool', [AT_H, STRING, OUTPUT(AT_BOOL)]) + +dllFunc('AT_SetEnumerated', [AT_H, STRING, c_int]) +dllFunc('AT_SetEnumeratedString', [AT_H, STRING, STRING]) +dllFunc('AT_GetEnumerated', [AT_H, STRING, OUTPUT(c_int)]) +dllFunc('AT_GetEnumeratedCount', [AT_H, STRING, OUTPUT(c_int)]) +dllFunc('AT_IsEnumeratedIndexAvailable', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) +dllFunc('AT_IsEnumeratedIndexImplemented', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) +dllFunc('AT_GetEnumeratedString', [AT_H, STRING, c_int, OUTSTRING, OUTSTRLEN]) + +dllFunc('AT_SetEnumIndex', [AT_H, STRING, c_int]) +dllFunc('AT_SetEnumString', [AT_H, STRING, STRING]) +dllFunc('AT_GetEnumIndex', [AT_H, STRING, OUTPUT(c_int)]) +dllFunc('AT_GetEnumCount', [AT_H, STRING, OUTPUT(c_int)]) +dllFunc('AT_IsEnumIndexAvailable', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) +dllFunc('AT_IsEnumIndexImplemented', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) +dllFunc('AT_GetEnumStringByIndex', [AT_H, STRING, c_int, OUTSTRING, OUTSTRLEN]) + +dllFunc('AT_Command', [AT_H, POINTER(AT_WC)]) + +dllFunc('AT_SetString', [AT_H, STRING, STRING]) +dllFunc('AT_GetString', [AT_H, STRING, OUTSTRING, OUTSTRLEN]) +dllFunc('AT_GetStringMaxLength', [AT_H, STRING, OUTPUT(c_int)]) + +dllFunc('AT_QueueBuffer', [AT_H, POINTER(AT_U8), c_int]) +dllFunc('AT_WaitBuffer', [AT_H, OUTPUT(POINTER(AT_U8)), OUTPUT(c_int), c_uint]) +dllFunc('AT_Flush', [AT_H]) + +##################################### +##Utility library (for unpacking etc ...) +dllFunc('AT_InitialiseUtilityLibrary', lib='ATUTIL') +dllFunc('AT_FinaliseUtilityLibrary', lib='ATUTIL') +dllFunc('AT_ConvertBuffer', [POINTER(AT_U8), POINTER(AT_U8), AT_64, AT_64, AT_64, STRING, STRING], lib='ATUTIL') +dllFunc('AT_ConvertBufferUsingMetadata', [POINTER(AT_U8), POINTER(AT_U8), AT_64, STRING], lib='ATUTIL') + +#Initialize the utility library +InitialiseUtilityLibrary() \ No newline at end of file diff --git a/microscope/wavefront_sensors/AndorSDK3Camera.py b/microscope/wavefront_sensors/AndorSDK3Camera.py new file mode 100644 index 00000000..cc43ccfb --- /dev/null +++ b/microscope/wavefront_sensors/AndorSDK3Camera.py @@ -0,0 +1,160 @@ +#!/usr/bin/python + +############### +# SDK3Cam.py +# +# Copyright David Baddeley, 2012 +# d.baddeley@auckland.ac.nz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################ + + +import AndorSDK3 as SDK3 + +class ATProperty(object): + def connect(self, handle, propertyName): + self.handle = handle + self.propertyName = propertyName + + +class ATInt(ATProperty): + def getValue(self): + return SDK3.GetInt(self.handle, self.propertyName).value + + def setValue(self, val): + SDK3.SetInt(self.handle, self.propertyName, val) + + def max(self): + return SDK3.GetIntMax(self.handle, self.propertyName).value + + def min(self): + return SDK3.GetIntMin(self.handle, self.propertyName).value + + +class ATBool(ATProperty): + def getValue(self): + return SDK3.GetBool(self.handle, self.propertyName).value > 0 + + def setValue(self, val): + SDK3.SetBool(self.handle, self.propertyName, val) + + +class ATFloat(ATProperty): + def getValue(self): + return SDK3.GetFloat(self.handle, self.propertyName).value + + def setValue(self, val): + SDK3.SetFloat(self.handle, self.propertyName, val) + + def max(self): + return SDK3.GetFloatMax(self.handle, self.propertyName).value + + def min(self): + return SDK3.GetFloatMin(self.handle, self.propertyName).value + +class ATString(ATProperty): + def getValue(self): + return SDK3.GetString(self.handle, self.propertyName, 255).value + + def setValue(self, val): + SDK3.SetString(self.handle, self.propertyName, val) + + def maxLength(self): + return SDK3.GetStingMaxLength(self.handle, self.propertyName).value + +class ATEnum(ATProperty): + def getIndex(self): + return SDK3.GetEnumIndex(self.handle, self.propertyName).value + + def setIndex(self, val): + SDK3.SetEnumIndex(self.handle, self.propertyName, val) + + def getString(self): + return self.__getitem__(self.getIndex()) + + def setString(self, val): + SDK3.SetEnumString(self.handle, self.propertyName, val) + + def __len__(self): + return SDK3.GetEnumCount(self.handle, self.propertyName).value + + def __getitem__(self, key): + return SDK3.GetEnumStringByIndex(self.handle, self.propertyName, key, 255).value + + def getAvailableValues(self): + n = SDK3.GetEnumCount(self.handle, self.propertyName).value + + return [SDK3.GetEnumStringByIndex(self.handle, self.propertyName, i, 255).value for i in range(n) if SDK3.IsEnumIndexAvailable(self.handle, self.propertyName, i).value] + +class ATCommand(ATProperty): + def __call__(self): + return SDK3.Command(self.handle, self.propertyName) + +class camReg(object): + #keep track of the number of cameras initialised so we can initialise and finalise the library + numCameras = 0 + + @classmethod + def regCamera(cls): + if cls.numCameras == 0: + SDK3.InitialiseLibrary() + + cls.numCameras += 1 + + @classmethod + def unregCamera(cls): + cls.numCameras -= 1 + if cls.numCameras == 0: + SDK3.FinaliseLibrary() + +#make sure the library is intitalised +camReg.regCamera() + +def GetNumCameras(): + return SDK3.GetInt(SDK3.AT_HANDLE_SYSTEM, 'DeviceCount').value + +def GetSoftwareVersion(): + return SDK3.GetString(SDK3.AT_HANDLE_SYSTEM, 'SoftwareVersion', 255) + +class SDK3Camera(object): + def __init__(self, camNum): + '''camera initialisation - note that this should be called from derived classes + *AFTER* the properties have been defined''' + #camReg.regCamera() #initialise the library if needed + self.camNum = camNum + + + def Init(self): + self.handle = SDK3.Open(self.camNum) + self.connectProperties() + + + def connectProperties(self): + for name, var in self.__dict__.items(): + if isinstance(var, ATProperty): + var.connect(self.handle, name) + + def GetSerialNumber(self): + return SDK3.GetString(self.handle, 'SerialNumber', 64) + + + def shutdown(self): + SDK3.Close(self.handle) + SDK3.FinaliseLibrary() + print('Camera Closed and Andor library finalized.') + camReg.unregCamera() ## TODO: do we have to unregister? + + diff --git a/microscope/wavefront_sensors/AndorZyla.py b/microscope/wavefront_sensors/AndorZyla.py new file mode 100644 index 00000000..8cb4d13e --- /dev/null +++ b/microscope/wavefront_sensors/AndorZyla.py @@ -0,0 +1,970 @@ +#!/usr/bin/python + +############### +# AndorNeo.py +# +# Copyright David Baddeley, 2012 +# d.baddeley@auckland.ac.nz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################ + + +from AndorSDK3Camera import * +import numpy as np +import threading +import Queue +import Pyro4 +# Config Pyro4 to use pickle as serializer +Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') +Pyro4.config.SERIALIZERS_ACCEPTED.remove('serpent') +Pyro4.config.SERIALIZER = 'pickle' +import time +import traceback +import gc +import atexit + +import memoryHandler +# import ctypes +# import os + + +## Some configuration parameters + +# Needed to keep the daemon from only listening to requests originating from the local host. +MY_IP_ADDRESS = '10.6.19.30' + +# CameraNumber is by default 0, the first non sumulated camera +CAMERA_NUMBER = 0 + +# Memory we want to allocate for the camera buffers in Mb +MEMORY_ALLOCATION = 1000 + +# Cropping modes. We keep these presets for convenience. +croppingModes = ('CROP_FULL', # 0 + 'CROP_1024', # 1 + 'CROP_512', # 2 + 'CROP_256', # 3 + 'CROP_128', # 4 + 'CROP_ARBITRARY' # 5 + ) + +croppingModesSizes = {'CROP_FULL': (2048,2048), # TODO: implement this depending on camera model + 'CROP_1024': (1024,1024), + 'CROP_512': (512,512), + 'CROP_256': (256,256), + 'CROP_128': (128,128), + 'CROP_ARBITRARY': (2048,2048) + } + +# Binning modes +BINNING_MODES = (u'1x1', u'2x2', u'3x3', u'4x4', u'8x8') +BINNING_VALUES = (1, 2, 3, 4, 8) + +# Trigger modes +# TODO: Should get this directly from the camera +(TRIGGER_INTERNAL, TRIGGER_SOFTWARE, TRIGGER_EXTERNAL, TRIGGER_EXTERNAL_START, TRIGGER_EXTERNAL_EXPOSURE) = (1, 4, 6, 2, 3) + +# Pixel encodings +PIXEL_ENCODING_MODES = ['Mono12', 'Mono12Packed', 'Mono16', 'Mono32'] + +# Acquisition Modes +(MODE_FIXED, MODE_CONTINUOUS) = (0, 1) + +# A black image +STATIC_BLACK = np.zeros((512, 512), dtype = np.uint16) # TODO: verify this is used + + +## Some helper classes and functions + +def resetCam(func): + ''' + Decorator function to put the camera in a mode where it can be + interacted with. Mostly needed to stop acquisitions. + ''' + def wrappedFunc(inst, *args, **kwargs): + inst.stopAcquisition() + inst.TriggerMode.setString(u'Internal') + inst.CycleMode.setString(u'Fixed') + inst.FrameCount.setValue(1) + + func(inst, *args, **kwargs) + +# print(func.__name__) +# print(args) +# print('Trigger: ' + str(inst.getTrigger())) + + +# # Start the acquisition with external triggers +# if inst.currentTrigger not in (TRIGGER_INTERNAL, TRIGGER_SOFTWARE): +# inst.startAcquisition() + + return wrappedFunc + + +class DataThread(threading.Thread): + ''' + This class retrieves images from the camera, and sends them to our client. + ''' + def __init__(self, parent, width, height): + threading.Thread.__init__(self) + + ## Loop back to parent to be able to communicate with it. + self.parent = parent + + ## Image dimensions, which we need for when we retrieve image + # data. Our parent is responsible for updating these for us. + self.width = self.height = 0 + ## Lock on modifying the above. + self.sensorLock = threading.Lock() + + ## Connection to client + self.clientConnection = None + + ## Whether or not we should unload images from the camera + self.shouldSendImages = True + + ## Whether the Datathread amin loop should run or not: + self.shouldRun = True + + ## Initial timestamp that we will use in conjunction with time.clock() + # to generate high-time-resolution timestamps. Just using time.time() + # straight-up on Windows only has accuracy of ~15ms. + self.initialTimestamp = time.time() + time.clock() + + ## Offset image array to subtract off of each image we receive. + self.offsetImage = None + + ## Pull images from self.imageQueue and send them to the client. + def run(self): + count = 0 + gTime = None + getTime = 0 + fixTime = 0 + sendTime = 0 + while self.shouldRun: + # This will block indefinitely until images are available. + with self.sensorLock: + try: + start = time.clock() + image = self.parent.getImage() + getTime += (time.clock() - start) + except Exception, e: + if 'getImage failed' not in e: + print "Error in getImage:",e + # Probably a timeout; just try again. + continue + # \todo This timestamp is potentially bogus if we get behind in + # processing images. + timestamp = time.clock() + self.initialTimestamp + start = time.clock() + image = self.fixImage(image) + fixTime += time.clock() - start + count += 1 + if count % 100 == 0: + # Periodically manually invoke the garbage collector, to + # ensure that we don't build up a giant pile of work that + # would interfere with our average write speed. + if gTime is None: + gTime = time.time() + delta = time.time() - gTime + gTime = time.time() + getTime = fixTime = sendTime = 0 + gc.collect() + + if image is not None and self.shouldSendImages and self.clientConnection is not None: + try: + start = time.clock() + self.clientConnection.receiveData('new image', image, timestamp) + cost = time.clock() - start + if cost > .5: + print "Took %.2fs to send to client" % cost + sendTime += cost + except Exception, e: + print "Failed to send image to client: %s", e + traceback.print_exc() + elif image is not None and self.shouldSendImages and self.clientConnection is None: # For debugging we want to print if we have an image + print 'Acquired Image:' + print image[:10] + + ## Fix an image -- set its shape and apply any relevant correction. + def fixImage(self, image): + # image.shape = self.height, self.width + if self.offsetImage is not None and self.offsetImage.shape == image.shape: + # Apply offset correction. + image -= self.offsetImage + return image + + ## Update who we send image data to. + def setClient(self, connection): + self.clientConnection = connection + + ## Update our image dimensions. + def setImageDimensions(self, width, height): + with self.sensorLock: + self.width = width + self.height = height + + ## Update the image we use for offset correction. + def setOffsetCorrection(self, image): + self.offsetImage = image + + ## Retrieve our offset correction image. + def getOffsetCorrection(self): + return self.offsetImage + + ## Stop method to exit the Datathread + def stop(self): + self.shouldRun = False + +class AndorBase(SDK3Camera): + + def __init__(self, camNum): + # define properties + self.CameraAcquiring = ATBool() # Returns whether or not an acquisition is currently acquiring. + self.SensorCooling = ATBool() # Configures the state of the sensor cooling. Cooling is disabled by default at power up and must be enabled for the camera to achieve its target temperature. The actual target temperature can be set with the TemperatureControl feature where available. + + self.AcquisitionStart = ATCommand() # Starts an acquisition + self.AcquisitionStop = ATCommand() # Stops an acquisition + + self.CameraPresent = ATBool() # Returns whether the camera is connected to the system. Register a callback to this feature to be notified if the camera is disconnected. Notification of disconnection will not occur if CameraAcquiring is true, in this case AT_WaitBuffer will return an error. + + self.CycleMode = ATEnum() # Configures whether the camera will acquire a fixed length sequence or a continuous sequence. In Fixed mode the camera will acquire FrameCount number of images and then stop automatically. In Continuous mode the camera will continue to acquire images indefinitely until the AcquisitionStop command is issued. + self.ElectronicShutteringMode = ATEnum() # Configures which on-sensor electronic shuttering mode is used + self.FanSpeed = ATEnum() # Configures the speed of the fan in the camera + self.PixelEncoding = ATEnum() # Configures the format of data stream. + self.PixelReadoutRate = ATEnum() # Configures the rate of pixel readout from the sensor. + self.TriggerMode = ATEnum() # Allows the user to configure the camera trigger mode at a high level. If the trigger mode is set to Advanced then the Trigger Selector and Trigger Source feature must also be set. + +# self.IsImplemented = ATBool() # Indicates whether the camera has implemented the feature specified. +# self.IsReadable = ATBool() # Indicates whether the feature specified can currently be read. +# self.IsWritable = ATBool() # Indicates whether the feature specified can currently be written. +# self.IsReadOnly = ATBool() # Indicates whether the feature specified can be modified. + + self.AOIHeight = ATInt() # Configures the Height of the sensor area of interest in super-pixels. + self.AOILeft = ATInt() # Configures the left hand coordinate of the sensor area of interest in sensor pixels. + self.AOITop = ATInt() # Configures the top coordinate of the sensor area of interest in sensor pixels. + self.AOIWidth = ATInt() # Configures the Width of the sensor area of interest in super-pixels. + self.PixelHeight = ATFloat() # Returns the height of each pixel in micrometers. + self.PixelWidth = ATFloat() # Returns the width of each pixel in micrometers. + + self.AOIHBin = ATInt() # Configures the Horizontal Binning of the sensor area of interest. + self.AOIVBin = ATInt() # Configures the Vertical Binning of the sensor area of interest. + self.AOIBinning = ATEnum() # Sets up pixel binning on the camera. Options: 1x1, 2x2, 3x3, 4x4, 8x8 + + self.FrameCount = ATInt() # Configures the number of images to acquire in the sequence. The value of FrameCount must be any value which is a multiple of AccumulateCount. This ensures the accumulation contains the correct number of frames. When this feature is unavailable then the camera does not currently support fixed length series, therefore you must explicitly abort the acquisition once you have acquired the amount of frames required. + self.ImageSizeBytes = ATInt() # Returns the buffer size in bytes required to store the data for one frame. This will be affected by the Area of Interest size, binning and whether metadata is appended to the data stream. + self.SensorHeight = ATInt() # Returns the height of the sensor in pixels. + self.SensorWidth = ATInt() # Returns the width of the sensor in pixels. + + self.CameraModel = ATString() # Returns the camera model + self.SerialNumber = ATString() # Returns the camera serial number + + self.ExposureTime = ATFloat() # The requested exposure time in seconds. Note: In some modes the exposure time can also be modified while the acquisition is running + self.FrameRate = ATFloat() # Configures the frame rate in Hz at which each image is acquired during any acquisition sequence. This is the rate at which frames are acquired by the camera which may be different from the rate at which frames are delivered to the user. For example when AccumulateCount has a value other than 1, the apparent frame rate will decrease proportionally. + self.SensorTemperature = ATFloat() # Read the current temperature of the sensor. + + #end auto properties + + + # Initialize the camera + SDK3Camera.__init__(self, camNum) + SDK3Camera.Init(self) + + # cache some properties that we have to access regularly or without + # interfering with the camera + self.serialNumber = self.SerialNumber.getValue() + self.cameraModel = self.CameraModel.getValue() + + self.CCDWidth = self.SensorWidth.getValue() + self.CCDHeight = self.SensorHeight.getValue() + + self.width = self.AOIWidth.getValue() + self.height = self.AOIHeight.getValue() + self.currentAOIHBin = 1 + self.currentAOIVBin = 1 + + self.currentTrigger = self.TriggerMode.getIndex() + self.currentElectronicShutteringMode = self.ElectronicShutteringMode.getIndex() + self.currentExposureTime = self.ExposureTime.getValue() + self.currentFrameRate = self.FrameRate.getValue() + self.currentFrameCount = self.FrameCount.getValue() + self.currentAcquisitionMode = self.CycleMode.getIndex() + self.currentPixelReadoutRate = self.PixelReadoutRate.getString() + self.currentPixelEncoding = self.PixelEncoding.getString() + + # Set the cropMode + self.curCropMode = 0 + + + + # Set some of the default camera settings + self.SensorCooling.setValue(True) +# self.setCrop(2) + self.setPixelEncoding(PIXEL_ENCODING_MODES.index('Mono16')) + + # Print some of the camera infos + print('Camera model: ' + self.getCameraModel()) + print('Camera serial number: ' + self.getSerialNumber()) + print('CCD sensor shape: ' + str(self.width) + 'x' + str(self.height)) + print('Pixel encoding: ' + self.getPixelEncoding()) + print('Shutter mode: ' + str(self.getElectronicShutteringMode())) + print('Fan speed: ' + self.FanSpeed.getString()) + print('Sensor cooling: ' + str(self.SensorCooling.getValue())) + print('Sensor temperature: ' + str(self.SensorTemperature.getValue())) + + # Create a memoryHandler instance to manage the camera buffers + self.memoryHandler = memoryHandler.MemoryHandler(self.handle) + + # Create a DataThread instance to manage the images transfer to the client + self.dataThread = DataThread(self, self.width, self.height) + + self.dataThread.start() + + ## Some methods to manage memory + + def allocMemory(self, timeout = 1000): + ''' + Allocate memory to store images + timeout is in millisec + ''' + strides = self.getStrides() + imageBytes = self.ImageSizeBytes.getValue() + + # We allocate about 500MB of RAM to the image buffer. Allocating + # too much memory seems to cause slowdowns and crashes, oddly enough. + numBuffers = (MEMORY_ALLOCATION * 1024 * 1024) / imageBytes + + print('Allocating memory for:') # TODO: remove this + print('numBuffers: ' + str(numBuffers)) + print('imageBytes: ' + str(imageBytes)) + print('imageWidth: ' + str(self.width)) + print('imageHeight: ' + str(self.height)) + print('strides: ' + str(strides)) + + self.memoryHandler.allocMemory(numBuffers = numBuffers, + imageBytes = imageBytes, + imageWidth = self.width, + imageHeight = self.height, + strides = strides, + timeout = timeout + ) + + def getStrides(self): + ''' + Returns the strides on the images array. + This is the behavior for a default SimCam and must be overridden in a + hardware camera. + ''' + return self.width * 16 # TODO: Not sure about this + + + ## Some methods to manage data transfer + + def receiveClient(self, uri): + ''' + Get told who we should be sending image data to, and how we + should mark ourselves when we do. + ''' + print('Receiving new client ' + str(uri)) + if uri is None: + self.dataThread.setClient(None) + self.dataThread.shouldSendImages = False + else: + connection = Pyro4.Proxy(uri) + connection._pyroTimeout = 5 + self.dataThread.setClient(connection) + self.dataThread.shouldSendImages = True + + def startAcquisition(self): + ''' + Starts the acquisition + ''' + # Make sure that camera is not acquiring + if not self.CameraAcquiring.getValue(): + try: + self.AcquisitionStart() + except Exception as e: + print('Could not start acquisition') + print(e) + + def stopAcquisition(self): + ''' + Stops the acquisition + ''' + if self.CameraAcquiring.getValue(): + try: + self.AcquisitionStop() + except Exception as e: + print('Could not stop acquisition') + print(e) + + def goSilent(self): + ''' + Stop sending images to the client, even if we still + receive them. + ''' + print "Switching to quiet mode" + with self.dataThread.sensorLock: + self.dataThread.shouldSendImages = False + + def goLoud(self): + ''' + Start sending new images to the client again. + ''' + print "Switching to loud mode" + with self.dataThread.sensorLock: + self.dataThread.shouldSendImages = True + + def getImage(self, timeout = .5): + ''' + get a single image wrapped around the memoryHandler getImage + ''' + return self.memoryHandler.getImage(timeout) + + def getImages(self, numImages, timeout = .5): + ''' + Retrieve the specified number of images. This may fail + if we ask for more images than we have, so the caller + should be prepared to catch exceptions. + ''' + result = [] + for i in xrange(numImages): + image = self.getImage(timeout) + result.append(image) + return result + + def discardImages(self, numImages = None): + ''' + Discard the specified number of images from the queue, + defaulting to all of them. + ''' + count = 0 + while count != numImages: + try: + self.getImage(0) + count += 1 + except Exception, e: + # Got an exception, probably because the + # queue is empty, so we're all done here. + return + + ## Camera methods + + @resetCam + def setTrigger(self, triggerMode): + ''' + Changes the triggering mode of the camera + ''' + try: + self.TriggerMode.setIndex(triggerMode) + except Exception as e: + print('Could not change the trigger mode:') + print e + return + + self.currentTrigger = triggerMode + + # Start the acquisition with external triggers + if triggerMode not in (TRIGGER_INTERNAL, TRIGGER_SOFTWARE): + self.CycleMode.setIndex(MODE_CONTINUOUS) + self.startAcquisition() + + + def getTrigger(self): + ''' + Returns the triggering mode of the camera + ''' + return self.currentTrigger + + def getTriggerString(self): + ''' + Returns the current trigger mode as a string + ''' + return self.TriggerMode.getString() + + @resetCam + def setElectronicShutteringMode(self, isGlobal): + ''' + Changes the shutter mode.0 is rolling shutter; 1 is global + ''' + try: + self.ElectronicShutteringMode.setIndex(isGlobal) + except Exception, e: + print('Could not change Electronic shuttering mode') + print(e) + return + + self.currentElectronicShutteringMode = isGlobal + + def getElectronicShutteringMode(self): + ''' + Get the current shutter mode. 0 is rolling shutter; 1 is global + ''' + return self.currentElectronicShutteringMode + + @resetCam # Seems that exposure time can be changed without stopping acquisition + def setExposureTime(self, time): + ''' + Changes the exposure time in the camera. In seconds + ''' + try: + self.ExposureTime.setValue(time) + except Exception as e: + print('ExposureTime could not be set:') + print(e) + return + + self.currentExposureTime = time + + def getExposureTime(self): + ''' + Returns the exposure time of the camera. In seconds + ''' + return self.currentExposureTime + + def getMinExposureTime(self): + ''' + Returns the minimum exposure time accepted by the camera. In seconds + ''' + return self.ExposureTime.min() + + def setCrop(self, mode): + ''' + Set the cropping mode to one of a few presets. + In version 2 of the SDK, these were the only valid crop modes; in + version 3, we can set any crop mode we want, but these + presets are still convenient. + mode is an integer. + ''' + self.setCropSize(croppingModesSizes[croppingModes[mode]]) + self.curCropMode = mode + + def setCropSize(self, cropSize, binning = 0): + ''' + Changes the AOI in the camera. + + cropSize is a tupple or list of two integers providing the size of the AOI (x, y). + binning must be a string + AOI will be centered in the camera + ''' + # cropSize must be converted into superpixel size in case there is binning + binnedCropSize = cropSize + if binning != 0: + binnedCropSize[0] = cropSize[0] // BINNING_VALUES[binning] + binnedCropSize[1] = cropSize[1] // BINNING_VALUES[binning] + + self.setCropArbitrary(binnedCropSize[0], + ((self.CCDWidth - cropSize[0]) // 2) + 1, + binnedCropSize[1], + ((self.CCDHeight - cropSize[1]) // 2) + 1, + binning, + ) + + @resetCam + def setCropArbitrary(self, width, left, height, top, binning = 0): + ''' + Changes arbitrarily the camera AOI + ''' + try: + # Set binning + self.AOIBinning.setIndex(binning) + + # Set AOI + self.AOIWidth.setValue(width) + self.AOILeft.setValue(left) + self.AOIHeight.setValue(height) + self.AOITop.setValue(top) + except Exception as e: + print('Could not change cropping mode') + print(e) + return + + # update width and height values + self.width = width + self.height = height + self.dataThread.setImageDimensions(width, height) + + # reallocate memory + self.allocMemory() + + # TODO: understand this + # TODO: should we return here the time shift for a pseudo global shuttering? + stride = self.getStrides() + div = float(stride) / width + return (stride, width, int(div) == div) + + def getCropMode(self): + return self.curCropMode + + def getImageShape(self): + ''' + Returns the image size (AOI) as a tupple of two integers (x, y) + ''' + return (self.width, self.height) + + def setOffsetCorrection(self, image): + ''' + Set an offset correction image to use. + ''' + self.dataThread.setOffsetCorrection(image) + + def getIsOffsetCorrectionOn(self): + ''' + Return true if a correction file is loaded for the current image dimensions. + ''' + correction = self.dataThread.getOffsetCorrection() + return correction is not None and correction.shape == (self.height, self.width) + + def getSerialNumber(self): + return self.serialNumber + + def getCameraModel(self): + return self.cameraModel + + @resetCam + def setIntegTime(self, iTime): + self.setExposureTime(iTime*1e-3) + self.setFrameRate(self.FrameRate.max()) + + def getIntegTime(self): + return self.currentExposureTime + + def getCycleTime(self): + return 1.0/self.currentFrameRate + + @resetCam + def setAcquisitionMode(self, mode): + try: + self.CycleMode.setIndex(mode) + except Exception as e: + print('Could not change AcquisitionMode (cycleMode):') + print(e) + return + + self.currentAcquisitionMode = mode + + def getAcquisitionMode(self): + return self.currentAcquisitionMode + + @resetCam + def setFrameRate(self, frameRate): + try: + self.FrameRate.setValue(frameRate) + except Exception as e: + print('Could not change the frame rate:') + print(e) + return + + self.currentFrameRate = frameRate + + def getFrameRate(self): + return self.currentFrameRate + + def getFrameCount(self): + return self.currentFrameCount + + @resetCam + def setFrameCount(self, frameCount): + ''' + ''' + try: + self.FrameCount.setValue(frameCount) + except Exception as e: + print('Could not change frame Count:') + print(e) + return + + self.currentFrameCount = frameCount + + @resetCam + def setPixelReadoutRate(self, rate): + ''' + Sets the PixelReadoutRate. + rate must be a string. For sCMOS: '280 MHz' or '100 MHz'. + For the SimCam: '550 MHz' + ''' + try: + self.PixelReadoutRate.setString(rate) + except Exception as e: + print('Could not change the pixel readout rate:') + print(e) + return + + self.currentPixelReadoutRate = rate + + def getPixelReadoutRate(self): + ''' + Returns the pixel readout rate as a string + ''' + return self.currentPixelReadoutRate + + @resetCam + def setPixelEncoding(self, index): + ''' + Sets the pixel encoding of the camera: + ''' + try: + self.PixelEncoding.setIndex(index) + except Exception as e: + print('Could not change the pixel encoding:') + print(e) + return + + self.currentPixelEncoding = PIXEL_ENCODING_MODES[index] + + def getPixelEncoding(self): + return self.currentPixelEncoding + + def getCCDWidth(self): + return self.CCDWidth + + def getCCDHeight(self): + return self.CCDHeight + + @resetCam + def setHorizBin(self, horizontalBinning): + try: + self.AOIHBin.setValue(horizontalBinning) + except Exception as e: + print('Could not change the horizontal Binning:') + print(e) + return + + self.currentAOIHBin = horizontalBinning + + def getHorizBin(self): + return self.currentAOIHBin + + @resetCam + def setVertBin(self, verticalBinning): + try: + self.AOIVBin.setValue(verticalBinning) + except Exception as e: + print('Could not change the vertical Binning:') + print(e) + return + + self.currentAOIVBin = verticalBinning + + def getVertBin(self): + return self.currentAOIVBin + + def getSensorTemperature(self): + '''for some reason querying the temperature takes a lot of time + Do it less often + ''' + return self.SensorTemperature.getValue() + + def getTemperatureStatus(self): + ''' + returns the status of the temperature sensor + ''' + return self.TemperatureStatus.getString() + + def isReady(self): + return True # TODO: implement this + + def getAOIWidth(self): + return self.width + + def getAOIHeight(self): + return self.height + + def abort(self): + ''' + Stop acquiring Images + ''' + self.stopAcquisition() + + def Shutdown(self): + self.abort() + print 'Going silent' + self.goSilent() + print 'Joining Datathread' + self.dataThread.stop() + self.dataThread.join() + print 'Stopping Memoryhandler' + self.memoryHandler.stop() + print 'Shutting down camera' + self.shutdown() + + def __del__(self): + self.Shutdown() + #self.compT.kill = True + + +class AndorZyla(AndorBase): + def __init__(self, camNum): + + #define properties + self.AOIStride = ATInt() # The size of one row in the image in bytes. Extra padding bytes may be added to the end of each line after pixel data to comply with line size granularity restrictions imposed by the underlying hardware interface. + + self.Baseline = ATInt() # Returns the baseline level of the image with current settings + + self.CameraName = ATString() # Returns the name of the camera. + + self.Overlap = ATBool() # Enables overlap readout mode. + self.SpuriousNoiseFilter = ATBool() # Enables or Disables the Spurious Noise Filter + self.StaticBlemishCorrection = ATBool() # Enables or Disables Static Blemish Correction + + self.VerticallyCentreAOI = ATBool() # Vertically centres the AOI in the frame. With this enabled, AOITop will be disabled. + + self.CameraDump = ATCommand() # Dumps current hardware configuration information to file in the executable directory. File is called camdump-Serial Number + self.SoftwareTrigger = ATCommand() # Generates a software trigger in the camera. Used to generate each frame on the camera whenever the trigger mode is set to Software. + + self.ExternalTriggerDelay = ATFloat() # Sets the delay time between the camera receiving an external trigger and the acquisition start. + self.FastAOIFrameRateEnable = ATBool() # Enables faster framerates at small AOIs. + self.RollingShutterGlobalClear = ATBool() # Enables Rolling Shutter Global Clear readout mode. + self.RowReadTime = ATFloat() # Configures the time in seconds to read a single row. + self.SensorReadoutMode = ATEnum() # Configures the direction in which the sensor will be read out + self.ShutterOutputMode = ATEnum() # Controls the mode the external trigger will run in. External Shutter signal can either be set to high (open) or low (closed). ShutterOutput can be triggered by setting AuxOutSourceTwo to ExternalShutterControl + + self.TemperatureStatus = ATEnum() # Reports the current state of cooling towards the Target Sensor Temperature. Read Only + self.SimplePreAmpGainControl = ATEnum() # Wrapper Feature to simplify selection of the sensitivity and dynamic range options. This feature should be used as a replacement for the PreAmpGainControl feature as some of the options in the PreAmpGainControl feature are not supported on all cameras. Supported Bit Depth will be dependent on the camera + self.BitDepth = ATEnum() # Returns the number bits used to store information about each pixel of the image. Supported Bit Depth will be dependent on the camera. + self.MetadataEnable = ATBool() # Enable metadata. This is a global flag which will enable inclusion of metadata in the data stream as described in Section 4.5 Metadata. When this flag is enabled the data stream will always contain the MetadataFrame information. This will override the subsequent metadata settings when disabled. + self.MetadataFrame = ATBool() # Indicates whether the MetadataFrame information is included in the data stream. This is read only and is automatically sent if metadata is enabled. + self.MetadataTimestamp = ATBool() # Enables inclusion of timestamp information in the metadata stream. The timestamp indicates the time at which the exposure for the frame started. + + self.ReadoutTime = ATFloat() # This feature will return the time to readout data from a sensor. + self.ExposedPixelHeight = ATInt() # Configures the exposure window in pixels. + + self.TimestampClock = ATInt() # Reports the current value of the camera internal timestamp clock. This same clock is used to timestamp images as they are acquired when the MetadataTimestamp feature is enabled. The clock is reset to zero when the camera is powered on and then runs continuously at the frequency indicated by the TimestampClockFrequency feature. The clock is 64-bits wide. + self.TimestampClockFrequency = ATInt() # Reports the frequency of the camera internal timestamp clock in Hz. + self.TimestampClockReset = ATCommand() # Resets the camera internal timestamp clock to zero. As soon as the reset is complete the clock will begin incrementing from zero at the rate given by the TimestampClockFrequency feature. + + self.AccumulateCount = ATInt() # Sets the number of images that should be summed to obtain each image in sequence. + self.Baseline = ATInt() # Returns the baseline level of the image with current settings + self.LUTIndex = ATInt() # Sets the position in the LUT to read/write a new pixel map + self.LUTValue = ATInt() # Sets the value in LUT in position specified by LUT Index + + self.ControllerID = ATString() # Returns a unique identifier for the camera controller device. i.e. Frame grabber over Cameralink + self.FirmwareVersion = ATString() # Returns the camera firmware version + + + AndorBase.__init__(self,camNum) + + def Init(self): + ''' + Will open the camera connection and set some default parameters + ''' + AndorBase.Init(self) + + print('Temperature status: ' + self.getTemperatureStatus()) + print('FirmwareVersion: ' + self.FirmwareVersion.getValue()) + print('Baseline level: ' + str(self.Baseline.getValue())) + + # Configure default camera status + self.setFanSpeed(u'Off') # TODO: get this from some configuration file + self.setSimplePreAmpGainControl(u'16-bit (low noise & high well capacity)') + self.setPixelReadoutRate(u'280 MHz') + + # store some values + self.currentAccumulateCount = self.AccumulateCount.getValue() + self.currentSimplePreAmpGainControl = self.SimplePreAmpGainControl.getString() + + # Define Zyla specific methods + + def getAccumulateCount(self): + return self.currentAccumulateCount + + @resetCam + def setAccumulateCount(self, nrOfFrames = 1): + try: + self.AccumulateCount.setValue(nrOfFrames) + except Exception as e: + print('Could not change accumulate count:') + print(e) + return + + self.currentAccumulateCount = nrOfFrames + + def getReadoutTime(self): + ''' + Returns the readout time in seconds as a float + ''' + return self.ReadoutTime.getValue() + + def getStrides(self): + ''' + Returns the strides on the images array. + This is the bahaviour for a default SimCam and must be overridden in a + hardware camera. + ''' + return self.AOIStride.getValue() + + def setFanSpeed(self, speed): + ''' + Sets the fan speed. For Zyla speed is 'On' or 'Off' + ''' + try: + self.FanSpeed.setString(speed) + except Exception as e: + print('Could not change the fan speed:') + print(e) + + @resetCam + def setSimplePreAmpGainControl(self, stringValue): + ''' + Sets the sensitivity and dynamic range options + stringValue is: + 11-bit (high well capacity) + 12-bit (high well capacity) + 11-bit (low noise) + 12-bit (low noise) + 16-bit (low noise & high well capacity) + ''' + try: + self.SimplePreAmpGainControl.setString(stringValue) + except Exception as e: + print('Could not change the preAmp gain control:') + print(e) + return + + self.currentSimplePreAmpGainControl = stringValue + +class AndorSim(AndorBase): + def __init__(self, camNum): + #define properties + self.SynchronousTriggering = ATBool() # Configures whether external triggers are synchronous with the read out of a sensor row. Asynchronous triggering may result in data corruption in the row being digitised when the triggers occurs. + + self.PixelCorrection = ATEnum() # Configures the pixel correction to be applied. + self.TriggerSelector = ATEnum() # Only if trigger mode in advanced + self.TriggerSource = ATEnum() # Only if trigger mode in advanced + + + AndorBase.__init__(self,camNum) + +if __name__ == '__main__': + try: + cam = AndorZyla(camNum = CAMERA_NUMBER) + daemon = Pyro4.Daemon(port = 7000, host = MY_IP_ADDRESS) + Pyro4.Daemon.serveSimple( + { + cam: 'Andorcam', + }, + daemon = daemon, ns = False, verbose = True + ) + + except Exception, e: + traceback.print_exc() + + def exitRemoteCamera(): + daemon.Daemon.close(self) + cam.Shutdown() + + atexit.register(exitRemoteCamera()()) + diff --git a/microscope/wavefront_sensors/ConsoleTests.py b/microscope/wavefront_sensors/ConsoleTests.py new file mode 100644 index 00000000..19247c13 --- /dev/null +++ b/microscope/wavefront_sensors/ConsoleTests.py @@ -0,0 +1,244 @@ +Python +2.7 +.13(v2 +.7 +.13: a06454b1afa1, Dec +17 +2016, 20: 42:59) [MSC v.1500 32 bit(Intel)] +on +win32 +Type +"copyright", "credits" or "license()" +for more information. + >> > import os, ctypes +>> > os.chdir('C:\Program Files (x86)\SID4_SDK\DLL SDK\BIN') +>> > SDK = ctypes.WinDLL('SID4_SDK') +>> > +>> > SDK +< WinDLL +'SID4_SDK', handle +3110000 +at +29 +b58f0 > +>> > UserProfileFile = 'C:\Program Files (x86)\SID4\phasics\SID4-079b default profile.txt' +>> > SDK.OpenSID4(UserProfileFile) +0 +>> > r = SDK.OpenSID4(UserProfileFile) +>> > r +0 +>> > OpenSID4 = getattr(SDK, 'OpenSID4') +>> > OpenSID4 +< _FuncPtr +object +at +0x02861648 > +>> > OpenSID4.restype +< class 'ctypes.c_long'> + +>> > OpenSID4.argtypes +>> > r +0 +>> > type(r) +< type +'int' > +>> > reference = ctypes.c_int +>> > refPointer = ctypes.POINTER(reference) +>> > refPointer +< class '__main__.LP_c_long'> + +>> > p = refPointer() +>> > p +< __main__.LP_c_long +object +at +0x02886AD0 > +>> > e = refPointer() +>> > e +< __main__.LP_c_long +object +at +0x02833F30 > +>> > e +< __main__.LP_c_long +object +at +0x02833F30 > +>> > OpenSID4 +< _FuncPtr +object +at +0x02861648 > +>> > OpenSID4(UserProfileFile, p, e) + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +OpenSID4(UserProfileFile, p, e) +ValueError: Procedure +probably +called +with too many arguments (12 bytes in excess) +>> > OpenSID4(p, e) + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +OpenSID4(p, e) +ValueError: Procedure +probably +called +with too many arguments (8 bytes in excess) +>> > OpenSID4() + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +OpenSID4() +WindowsError: exception: access +violation +writing +0x7332D7A6 +>> > OpenSID4(p) +0 +>> > OpenSID4(e) +0 +>> > e +< __main__.LP_c_long +object +at +0x02833F30 > +>> > p +< __main__.LP_c_long +object +at +0x02886AD0 > +>> > OpenSID4.argtypes +>> > OpenSID4 = getattr(SDK, 'OpenSID4') +>> > OpenSID4 +< _FuncPtr +object +at +0x02861648 > +>> > +KeyboardInterrupt +>> > class SDK_Reference(ctypes.Structure): + _fields_ = [('SDK_Reference', c_int)] + + +Traceback(most +recent +call +last): +File +"", line +1, in < module > + + +class SDK_Reference(ctypes.Structure): + + + File +"", line +2, in SDK_Reference +_fields_ = [('SDK_Reference', c_int)] +NameError: name +'c_int' is not defined +>> > class SDK_Reference(ctypes.Structure): + _fields_ = [('SDK_Reference', ctypes.c_int)] + +>> > UserProfileFile +'C:\\Program Files (x86)\\SID4\\phasics\\SID4-079b default profile.txt' +>> > UserProfileLocation = c_char_p(UserProfileFile) + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +UserProfileLocation = c_char_p(UserProfileFile) +NameError: name +'c_char_p' is not defined +>> > UserProfileLocation = ctypes.c_char_p(UserProfileFile) +>> > UserProfileLocation +c_char_p('C:\\Program Files (x86)\\SID4\\phasics\\SID4-079b default profile.txt') +>> > ErrorCode = ctypes.c_long() +>> > ErrorCodeRef = ctypes.byref(ErrorCode) +>> > ErrorCodeRef +< cparam +'P'(02 +99 +CC88) > +>> > SessionID = SDK_Reference() +>> > SessionIDRef = ctypes.byref(SessionID) +>> > OpenSID4(UserProfileLocation, SessioIDRef, ErrorCodeRef) + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +OpenSID4(UserProfileLocation, SessioIDRef, ErrorCodeRef) +NameError: name +'SessioIDRef' is not defined +>> > OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) +ValueError: Procedure +probably +called +with too many arguments (12 bytes in excess) +>> > SDK1 = ctypes.CDLL('SID4_SDK') +>> > SDK1.OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) +0 +>> > SessionID +< __main__.SDK_Reference +object +at +0x0299CCB0 > +>> > SessionID.value + +Traceback(most +recent +call +last): +File +"", line +1, in < module > +SessionID.value +AttributeError: 'SDK_Reference' +object +has +no +attribute +'value' +>> > SessionID +< __main__.SDK_Reference +object +at +0x0299CCB0 > +>> > ErrorCode +c_long(7017) \ No newline at end of file diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py new file mode 100644 index 00000000..b97432aa --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -0,0 +1,388 @@ +#!/usr/bin/python +# -*- coding: utf-8 +# +# Copyright 2017 Julio Mateos (julio.mateos-langerak@igh.cnrs.fr) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Phasics SID4 SDK python interface + +This module provides a python interface to Phasics SDK to control +SID4 waveform sensors. +""" + +import ctypes +from ctypes import POINTER, Structure, c_int, c_uint8, c_ushort, c_long, c_ulong, c_float, c_char, c_ubyte, c_uint, c_double, c_void_p + +# Type definitions +LVBoolean = c_uint8 # 8-bit integer. 0 if False, 1 if True +SDK_Reference = c_int # 32-bit integer used to define a unique reference ID +SDK_WC = ctypes.c_wchar +SDK_CHAR = c_char +SDK_INT = c_int +SDK_LONG = c_long +SDK_DOUBLE = c_double +SDK_ULONG = c_ulong +SDK_FLOAT = c_float +SDK_UCHAR = c_ubyte +SDK_USHORT = c_ushort + +class SDK_Reference(Structure): + _fields_ = [('SDK_Reference', SDK_INT)] + +class ArraySize(Structure): + """This structure contains information about 2D array size: + it gives the size of each dimension. + Note: this structure is used to initialize the size of all the array + containing the phase map information. + So this structure is often used in the SDK SID4 and particularly in the + Interferogram Analysis Function. + + :parameter nRow: Long integer that specifies the size of the first dimension, + in other words it gives the rows number + :parameter nCol: Long integer that specifies the size of the second dimension, + in other words it gives the columns number + """ + _fields_ = [('nRow', SDK_LONG), + ('nCol', SDK_LONG)] + +class AnalysisInfo(Structure): + """This structure type contains information on the user profile + currently in memory, such as its location on the hard drive, + the current reference file, grating position (in mm) + and the wavelength (in nm) used as the unit for the phase values. + The PhaseSize variable is very important since it allows sizing + the phase and intensity maps used as inputs for the interferogram + analysis functions. + + This structure is filled by the GetUserProfile function + + :parameter GratingPositionMm: Double precision float giving + the grating position in millimeters. + This distance corresponds to the one between the diffraction grating and the CCD sensor of the SID4 + :parameter wavelengthNm: Double precision float that gives the wevelength currently used in nanometer + :parameter RemoveBackgroundImage: Boolean specifying if the background image is removed or not: + - false (value 0): the image background is not removed + - true (value 1): the image background is removed + :parameter PhaseSize_width: Long integer specifying the first dimension size of the phase map. + In other words it gives the number of lines + :parameter PhaseSize_Height: Long integer specifying the second dimension size of the phase map. + In other words it gives the number of columns + """ + _fields_ = [('GratingPositionMm', SDK_DOUBLE), + ('wavelengthNm', SDK_DOUBLE), + ('RemoveBackgroundImage', LVBoolean), + ('PhaseSize_width', SDK_LONG), + ('PhaseSize_Height', SDK_LONG)] + +class CameraInfo(Structure): + """This structure type contains information on the camera settings + associated with the user profile currently in memory. + + The camera parameters like the frame rate are indicates + in order to remain the SDK compatible with older Teli cameras. + If your camera are not a Teli one the frame rate, trigger mode, + gain, exposure time parameter should not be taken into account. + On the other side the parameters pixel size and number of camera + recorded are always correct. + + Regardless of the type of camera you use, the parameters + of this one can be recovered through function Camera_GetAttribute. + + This structure is filled by the GetUserProfile function + :parameter FrameRate: The following values are to be taken into account only if your camera is a TELI: + Camera frame rate: 0 = "3.75hz", 1 = "7.5hz", 2 = "15hz", 3 = "30hz", 4 = "60hz" + :parameter TriggerMode: The following values are to be taken into account only if your camera is a TELI: + Camera trigger mode: 0 = continous mode; 1 = trigger mode0; 2 = trigger mode1. + :parameter Gain: The following values are to be taken into account only if your camera is a TELI: + Camera gain. The range is [40;208] + :parameter ExposureTime: The following values are to be taken into account only if your camera is a TELI: + Camera exposure time: 0 = "1/60s", 1 = "1/200s", 2 = "1/500s", 3 = "1/1000s", 4 = "1/2000s", 5 = "1/4000s", + 6 = "1/8000s", 7 = "1/20000s". + :parameter PixelSizeM: Size of the camera pixel (µm) + :parameter NumberOfCameraRecorded: Number of recorded camera in the SID4 SDK software + """ + _fields_ = [('FrameRate', SDK_LONG), + ('TriggerMode', SDK_ULONG), + ('Gain', SDK_LONG), + ('ExposureTime', SDK_ULONG), + ('PixelSizeM', SDK_FLOAT), + ('NumberOfCameraRecorded', SDK_UCHAR)] + +class ZernikeInformation(Structure): + """This structure type contains information on the projection base + and the number of polynomials. + + The base can be either Zernike or Legendre. + The number of polynomials depends on the value of the highest polynomial order + + :parameter Base: Unsigned short int that specifies the polynomials base (Zernike=0 and Legendre=1) + :parameter Polynomials: Long integer that specifies the number of polynomials + """ + _fields_ = [('Base', SDK_USHORT), + ('Polynomials', SDK_LONG)] + +class ZernikeParam(Structure): + """This structure type contains information useful for Zernike calculation. + It defines the size of the intensity map and the size of the mask + the program will use to compute the Zernike coefficients. + It also defines the projection base (Zernike or Legendre). + + :parameter ImageRowSize: Unsigned long integer that specifies the number of rows of the intensity map + :parameter ImageColSize: Unsigned long integer that specifies the number of columns of the intensity map + :parameter MaskRowSize: Unsigned long integer that specifies the number of rows of the mask map + :parameter MaskColSize: Unsigned long integer that specifies the number of columns of the mask map + :parameter Base: Unsigned short int that specifies the polynomials base (Zernike=0 and Legendre=1) + """ + _fields_ = [('ImageRowSize', SDK_ULONG), + ('ImageColSize', SDK_ULONG), + ('MaskRowSize', SDK_ULONG), + ('MaskColSize', SDK_ULONG), + ('Base', SDK_USHORT)] + +class TiltInfo(Structure): + """This structure type contains Tilt information (X and Y tilts) + removed from the output phase + + :parameter XTilt: X tilt removed from output phase given in milliradian + :parameter YTilt: Y tilt removed from output phase given in milliradian + """ + _fields_ = [('XTilt', SDK_FLOAT), + ('YTilt', SDK_FLOAT)] + +# Import the dll's + +_stdcall_libraries = {} +_stdcall_libraries['SID4_SDK'] = ctypes.WinDLL('SID4_SDK') + +# Get error codes + +NO_ERROR = 0 +# TODO: Get error codes from SID4_SDK_Constants.h through a subclass of Exception + +class DeviceError(Exception): + pass + +### Functions ### +STRING = POINTER(SDK_WC) + +# classes so that we do some magic and automatically add byrefs etc ... can classify outputs +class _meta(object): + pass + +class OUTPUT(_meta): + def __init__(self, val): + self.type = val + self.val = POINTER(val) + + def getVar(self, bufLen=0): + v = self.type() + return v, ctypes.byref(v) + + +class _OUTSTRING(OUTPUT): + def __init__(self): + self.val = STRING + + def getVar(self, bufLen=0): + v = ctypes.create_unicode_buffer(bufLen) + return v, v + +OUTSTRING = _OUTSTRING() + +class _OUTSTRLEN(_meta): + def __init__(self): + self.val = c_int + +OUTSTRLEN = _OUTSTRLEN() + +def stripMeta(val): + if isinstance(val, _meta): + return val.val + else: + return val + +class dllFunction(object): + def __init__(self, name, args=[], argnames=[], lib='SID4_SDK'): + self.f = getattr(_stdcall_libraries[lib], name) + self.f.restype = c_int + self.f.argtypes = [stripMeta(a) for a in args] + + self.fargs = args + self.fargnames = argnames + self.name = name + + self.inp = [not isinstance(a, OUTPUT) for a in args] + self.in_args = [a for a in args if not isinstance(a, OUTPUT)] + self.out_args = [a for a in args if isinstance(a, OUTPUT)] + + self.buf_size_arg_pos = -1 + for i in range(len(self.in_args)): + if isinstance(self.in_args[i], _OUTSTRLEN): + self.buf_size_arg_pos = i + + ds = name + '\n\nArguments:\n===========\n' + for i in range(len(args)): + an = '' + if i < len(argnames): + an = argnames[i] + ds += '\t%s\t%s\n' % (args[i], an) + + self.f.__doc__ = ds + + def __call__(self, *args): + ars = [] + i = 0 + ret = [] + + if self.buf_size_arg_pos >= 0: + bs = args[self.buf_size_arg_pos] + else: + bs = 255 + + for j in range(len(self.inp)): + if self.inp[j]: # an input + ars.append(args[i]) + i += 1 + else: # an output + r, ar = self.fargs[j].getVar(bs) + ars.append(ar) + ret.append(r) + # print r, r._type_ + + # print ars + res = self.f(*ars) + # print res + + if not res == NO_ERROR: + raise DeviceError(self.name, res) + + if len(ret) == 0: + return None + if len(ret) == 1: + return ret[0] + else: + return ret + +def dllFunc(name, args=[], argnames=[], lib='SID4_SDK'): + f = dllFunction(name, args, argnames, lib) + globals()[name] = f + +# Configuration Functions + +dllFunc('OpenSID4', + [SDK_CHAR, OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['UserProfileLocation[]', '*SessionID', '*ErrorCode']) +dllFunc('CloseSID4', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SessionID', '*ErrorCode']) +dllFunc('GetUserProfile', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, + SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, + SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, OUTPUT(AnalysisInfo), + OUTPUT(CameraInfo), SDK_CHAR, SDK_LONG, OUTPUT(ArraySize), OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'UserProfile_Name[]', 'uspName_bufSize', 'UserProfile_File[]', 'uspFile_bufSize', + 'UserProfile_Description[]', 'uspDesc_bufSize', 'UsrP_LatestReference[]', 'uspLastRef_bufSize', + 'UserProfileDirectory[]', 'uspDir_bufSize', 'SDKVersion[]', 'version_bufSize', '*AnalysisInformation', + '*CameraInformation', 'SNPhasics[]', 'SNPhasics_bufSize', '*AnalysisArraySize', '*ErrorCode']) +dllFunc('ChangeReference', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_USHORT, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'ReferencePath[]', 'ReferenceSource', 'ArchivedPath[]', 'ArchivedPath_bufSize', '*ErrorCode']) +dllFunc('ChangeMask', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', + 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_Contours_coordinates[]', 'contoursCoord_bufSize', + '*ErrorCode']) +dllFunc('SetBackground', + [OUTPUT(SDK_Reference), SDK_USHORT, SDK_CHAR, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'Source', 'BackgroundFile[]', 'UpdatedBackgroundImageFile[]', 'updatedImageFile_bufSize', + '*ErrorCode']) +dllFunc('LoadMaskDescriptorInfo', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', + 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', + '*ErrorCode']) +dllFunc('LoadMaskDescriptor', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', + 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', + '*ErrorCode']) +dllFunc('ModifyUserProfile', + [OUTPUT(SDK_Reference), OUTPUT(AnalysisInfo), SDK_USHORT, SDK_CHAR, SDK_CHAR, OUTPUT(LVBoolean), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*AnalysisInformation', 'ReferenceSource', 'ReferencePath[]', 'UserProfile_Decription[]', + '*ReferenceChanged', '*ErrorCode']) +dllFunc('NewUserProfile', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_CHAR, SDK_CHAR, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'CameraSNPhasics[]', 'ProfileName[]', 'UserProfileDirectory[]', 'ProfilePathFileOut[]', + 'pathFileOut_bufSize', '*ErrorCode']) +dllFunc('SaveMaskDescriptor', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, SDK_USHORT, SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', 'ROI_NbOfContours', + 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', + '*ErrorCode']) +dllFunc('SaveCurrentUserProfile', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) + +# Camera Control Functions + +dllFunc('CameraList', + [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'CameraList_SNPhasics[]', 'camList_bufSize', '*ErrorCode']) +# Obsolete +# dllFunc('CameraSetup', +# [OUTPUT(SDK_Reference), SDK_USHORT, SDK_USHORT, OUTPUT(SDK_LONG)], +# ['*SDKSessionID', 'CameraParameter', 'Value', '*ErrorCode']) +dllFunc('CameraInit', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('CameraStart', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('CameraStop', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('CameraClose', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('StartLiveMode', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('StopLiveMode', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*ErrorCode']) +dllFunc('Camera_GetAttribute', + [OUTPUT(SDK_Reference), SDK_USHORT, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'AttributeID', '*AttributeValueOut', '*ErrorCode']) +dllFunc('Camera_GetAttributeList', + [OUTPUT(SDK_Reference), SDK_USHORT, SDK_LONG, SDK_CHAR, SDK_LONG, + SDK_LONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'AttributeID[]', 'attribID_bufSize', 'AttributeName_SeparatedByTab[]', 'attribName_bufSize', + 'AttributeGmin[]', 'attribGmin_bufSize', 'AttributeGmax[]', 'attribGmax_bufSize', '*ErrorCode']) +dllFunc('Camera_SetAttribute', + [OUTPUT(SDK_Reference), SDK_USHORT, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'AttributeID', '*AttributeValue', '*ErrorCode']) +dllFunc('Camera_GetNumberOfAttribute', + [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG), OUTPUT(SDK_LONG)], + ['*SDKSessionID', '*NumberOfAttribute', '*ErrorCode']) +dllFunc('Camera_ConvertExposureMs', + [OUTPUT(SDK_Reference), SDK_DOUBLE, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'ExposureRawValueIn', '*ExposureValueMsOut', '*ErrorCode']) + +# Inteferogram Analysis functions + +dllFunc('ArrayAnalysis', + [OUTPUT(SDK_Reference), SDK_INT, SDK_LONG, SDK_FLOAT, SDK_LONG, + SDK_FLOAT, SDK_LONG, OUTPUT(TiltInfo), OUTPUT(ArraySize), OUTPUT(ArraySize), OUTPUT(SDK_LONG)], + ['*SDKSessionID', 'InterferogramInArrayI16[]', 'Interfero_bufSize', 'Intensity[]', 'Intensity_bufSize', + 'Phase[]', 'Phase_bufSize', '*TiltInformation', '*AnalysisArraySize', '*ImageCameraSize', '*ErrorCode']) \ No newline at end of file diff --git a/microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp b/microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp new file mode 100644 index 00000000..acf4a937 --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp @@ -0,0 +1,203 @@ +/* + © Copyright 2008 by PHASICS. + All rights reserved. + + @File: SID4_SDK_ChangeMask_Example.cpp + @Description: This example shows how to define the pupil analysis applied to + the interferogram by loading a mask file (.msk). + + This example involves 6 SID4 SDK's functions: + + "OpenSID4" loads the configuration file specified by "userProfile_File" in memory. + It returns a unique reference ID "SessionID" that should be used as an input + for all other SID4 SDK functions. + + "GetUserProfile" outputs the parameters currently used to analyse analyse interferograms + and the camera settings. + + "LoadMaskDescriptorInfo" returns the mask descriptor information from a "*.msk" file: + "Globalrectangle" gives the left, top, right and bottom position of the ROI global rectangle + "ROI_NbOfContours" indicates the number of sub-ROI defined in the chosen mask (main ROI). + "ROIinfo_Contours_info": array containing the sub-ROI characteristics, + there are three value for each one: + ID: this value refers to whether the contour is the external (0) or internal edge (1) + TypeValue: refers to the shape type of the contour: 3 = Rectangle, 4 = Oval or Circle + NumberOfCorrdinates: the number of points that defines the contour. + "ROIinfo_Contours_coordinates": array containing successively the coordinates of all sub-ROIs + + "ChangeMask" changes the current mask applied to the interferograms before any analysis. This + defines the analysis pupil. It can be changed by giving the path to a previously saved mask file + (.msk files saves with the phase and intensity files) or by giving manually the definition + of a Region of Interedt (ROI) which indicates the position and shape of the desired pupil. + + "FileAnalysis" analyses the interferogram. It ouputs a Phase and Intensity map, Tilt information + (X and Y tilts) removed from the ouput phase. + + "CloseSID4" closes the SID4 session. It releases memory devoted to the session. +*/ + + +#include +#include // for memory allocation +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +const int bufSize=1024; + + +void main(void) +{ + char MaskFile1[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask2.msk"; + char MaskFile2[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask3.msk"; + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; + SDK_Reference SessionID=0; + int i,nrow, ncol; + long Error=0; + char UserProfile_Name[bufSize]=""; + long uspName_bufSize = bufSize; + char UserProfile_File[bufSize]=""; + long uspFile_bufSize = bufSize; + char UserProfile_Description[bufSize]=""; + long uspDesc_bufSize = bufSize; + char UsrP_LastReference[bufSize]=""; + long uspLastRef_bufSize = bufSize; + char UserProfile_Directory[bufSize]=""; + long uspDir_bufSize = bufSize; + char SDKVersion[bufSize]=""; + long version_bufSize = bufSize; + AnalysisInfo AnalysisInformation; + CameraInfo CameraInformation; + char SNPhasics[bufSize]=""; + long SNPhasics_bufSize = bufSize; + ArraySize ImageSize; + + // Open SID4 Session + OpenSID4(userProfile_File,&SessionID,&Error); + if(!Error) + { printf ("************************ SID4 Session correctly opened **********************\n"); + printf ("SessionID=%d; Error=%d\n",SessionID,Error); + } + + // Reading of parameters currently used for interferogram analysis + GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, + UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, + UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, + &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); + + // Array dimension for Phase and Intensity + nrow = AnalysisInformation.PhaseSize_Height; + ncol = AnalysisInformation.PhaseSize_width; + + // memory allocation for Phase and Intensity before calling the FileAnalysis function + TiltInfo TiltInformation; + long Intensity_bufSize = nrow*ncol; + long Phase_bufSize = nrow*ncol; + ArraySize AnalysisArraySize; + + AnalysisArraySize.height=nrow; + AnalysisArraySize.width=ncol; + + float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); + float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); + + TiltInformation.XTilt=0; + TiltInformation.YTilt=0; + + + // Definition of the ROI Descriptor (Mask) which will be applied to the interferogram + // before any analysis + + //ROI Descriptor defintion + long ROI_GlobalRectangle[4]; //poition left, top, right and bottom side of the ROI global rectangle + long globalRect_bufSize = 4; // size of the ROI_GlobalRectangle array + unsigned short int ROI_NbOfContours; // gives the number of sub-ROI defined in the chosen mask (main ROI). + unsigned long ROIinfo_Contours_info[bufSize]; //array containing the sub-ROI characteristics, there are three value for each one: ID,TypeValue, NumberOfCorrdinates + long contoursInfo_bufSize = bufSize; + long ROIinfo_Contours_coordinates[bufSize]; //array containing successively the coordinates of all sub-ROIs + long contoursCoord_bufSize = bufSize; + + // we want to use the mask defined in the "Mask2.msk" file as the analysis pupil. + // Before to use the "ChangeMask" function to set the analysis pupil, it is necessary + // to get first the ROI descriptor information using the "LoadMaskDescriptorInfo" function + // in order to initialize the input parameters of the "ChangeMask" function. + + LoadMaskDescriptorInfo(&SessionID, MaskFile1, ROI_GlobalRectangle, globalRect_bufSize, + &ROI_NbOfContours, ROIinfo_Contours_info, contoursInfo_bufSize, ROIinfo_Contours_coordinates, + contoursCoord_bufSize, &Error); + + unsigned long *ROI_1_Contours_info =(unsigned long*)calloc(3*ROI_NbOfContours,sizeof(unsigned long)); + + int j=0; + int TotalNumberOfCoord = 0; + for(i=0;i +#include +#include // for memory allocation +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +const int bufSize=1024; + + +void main(void) +{ + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; + char MaskFile1[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask2.msk"; + char MaskFile[]=""; + SDK_Reference SessionID=0; + int nrow, ncol; + long Error=0; + char UserProfile_Name[bufSize]=""; + long uspName_bufSize = bufSize; + char UserProfile_File[bufSize]=""; + long uspFile_bufSize = bufSize; + char UserProfile_Description[bufSize]=""; + long uspDesc_bufSize = bufSize; + char UsrP_LastReference[bufSize]=""; + long uspLastRef_bufSize = bufSize; + char UserProfile_Directory[bufSize]=""; + long uspDir_bufSize = bufSize; + char SDKVersion[bufSize]=""; + long version_bufSize = bufSize; + AnalysisInfo AnalysisInformation; + CameraInfo CameraInformation; + char SNPhasics[bufSize]=""; + long SNPhasics_bufSize = bufSize; + ArraySize ImageSize; + + // Open SID4 Session + OpenSID4(userProfile_File,&SessionID,&Error); + if(!Error) + { printf ("************************ SID4 Session correctly opened **********************\n"); + printf ("SessionID=%d; Error=%d\n",SessionID,Error); + } + else{ + printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); + exit(1); + } + + + // Definition of the ROI Descriptor (Mask) which will be applied to the interferogram + // before any analysis + + //ROI Descriptor defintion + long ROI_GlobalRectangle[4]; //poition left, top, right and bottom side of the ROI global rectangle + long globalRect_bufSize = 4; // size of the ROI_GlobalRectangle array + unsigned short int ROI_NbOfContours; // gives the number of sub-ROI defined in the chosen mask (main ROI). + unsigned long ROIinfo_Contours_info[bufSize]; //array containing the sub-ROI characteristics, there are three value for each one: ID,TypeValue, NumberOfCorrdinates + long contoursInfo_bufSize = bufSize; + long ROIinfo_Contours_coordinates[bufSize]; //array containing successively the coordinates of all sub-ROIs + long contoursCoord_bufSize = bufSize; + + // we want to use the mask defined in the "Mask2.msk" file as the analysis pupil. + // Before to use the "ChangeMask" function to set the analysis pupil, it is necessary + // to get first the ROI descriptor information using the "LoadMaskDescriptorInfo" function + // in order to initialize the input parameters of the "ChangeMask" function. + + LoadMaskDescriptorInfo(&SessionID, MaskFile1, ROI_GlobalRectangle, globalRect_bufSize, + &ROI_NbOfContours, ROIinfo_Contours_info, contoursInfo_bufSize, ROIinfo_Contours_coordinates, + contoursCoord_bufSize, &Error); + + unsigned long *ROI_1_Contours_info =(unsigned long*)calloc(3*ROI_NbOfContours,sizeof(unsigned long)); + + int i, j=0; + int TotalNumberOfCoord = 0; + for(i=0;i +#include +#include +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +const int bufSize=1024; + +void main(void) +{ + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; + SDK_Reference SessionID=0; + int nrow, ncol; + long Error=0; + char UserProfile_Name[bufSize]=""; + long uspName_bufSize = bufSize; + char UserProfile_File[bufSize]=""; + long uspFile_bufSize = bufSize; + char UserProfile_Description[bufSize]=""; + long uspDesc_bufSize = bufSize; + char UsrP_LastReference[bufSize]=""; + long uspLastRef_bufSize = bufSize; + char UserProfile_Directory[bufSize]=""; + long uspDir_bufSize = bufSize; + char SDKVersion[bufSize]=""; + long version_bufSize = bufSize; + AnalysisInfo AnalysisInformation; + CameraInfo CameraInformation; + char SNPhasics[bufSize]=""; + long SNPhasics_bufSize = bufSize; + ArraySize ImageSize; + + // Open SID4 Session + OpenSID4(userProfile_File,&SessionID,&Error); + if(!Error) + { printf ("************************ SID4 Session correctly opened **********************\n"); + printf ("SessionID=%d; Error=%d",SessionID,Error); + } + else{ + printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); + exit(1); + } + + // Reading of parameters currently used for interferogram analysis + GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, + UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, + UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, + &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); + + // Array dimension for Phase and Intensity + nrow = AnalysisInformation.PhaseSize_Height; + ncol = AnalysisInformation.PhaseSize_width; + + //// memory allocation for Phase and Intensity before calling FileAnalysis + TiltInfo TiltInformation; + long Intensity_bufSize = nrow*ncol; + long Phase_bufSize = nrow*ncol; + ArraySize AnalysisArraySize; + + AnalysisArraySize.height=nrow; + AnalysisArraySize.width=ncol; + + float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); + float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); + + + // Interferogram Analysis. We get in output Phase and Intensity map, tiltInformation + FileAnalysis(&SessionID, &AnalysisArraySize, inteferogram_File, Intensity, Intensity_bufSize, + Phase, Phase_bufSize, &TiltInformation, &Error); + if(!Error) + { printf ("\nXtilt=%f; Ytilt=%f\n",TiltInformation.XTilt,TiltInformation.YTilt); + } + else{ + printf ("\nThe error %d occured in the FileAnalysis function!\n\n",Error); + exit(1); + } + + // Close the SID4 session + CloseSID4(&SessionID,&Error); + + // Memory release + free(Intensity); + free(Phase); + +} + diff --git a/microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp b/microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp new file mode 100644 index 00000000..22feead9 --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp @@ -0,0 +1,104 @@ +/* + © Copyright 2008 by PHASICS. + All rights reserved. + + @File: SID4_SDK_GrabImage_Example.cpp + @Description: This example shows how to grab an interferogram from the current camera. + + 4 functions are involved: + + OpenSID4 loads the configuration file specified by "userProfile_File" in memory. + It returns a unique reference ID "SessionID" that should be used as an input + for all other SID4 SDK functions. + + GetUserProfile outputs the current camera settings. + + GrabImage grabs an interferogram from the camera. It initializes the camera according the + current camera settings, grabs an image (2D int16 Array) and stops the acquisition. + + CloseSID4 closes the SID4 session. It releases memory devoted to the session. +*/ + + +#include +#include +#include // for memory allocation +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +const int bufSize=1024; + + +void main(void) +{ + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + SDK_Reference SessionID=0; + int nrow, ncol; + long Error=0; + char UserProfile_Name[bufSize]=""; + long uspName_bufSize = bufSize; + char UserProfile_File[bufSize]=""; + long uspFile_bufSize = bufSize; + char UserProfile_Description[bufSize]=""; + long uspDesc_bufSize = bufSize; + char UsrP_LastReference[bufSize]=""; + long uspLastRef_bufSize = bufSize; + char UserProfile_Directory[bufSize]=""; + long uspDir_bufSize = bufSize; + char SDKVersion[bufSize]=""; + long version_bufSize = bufSize; + AnalysisInfo AnalysisInformation; + CameraInfo CameraInformation; + char SNPhasics[bufSize]=""; + long SNPhasics_bufSize = bufSize; + ArraySize ImageCameraSize; + + // Open SID4 Session + OpenSID4(userProfile_File,&SessionID,&Error); + if(!Error) + { printf ("************************ SID4 Session correctly opened **********************\n"); + printf ("SessionID=%d; Error=%d\n",SessionID,Error); + } + else{ + printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); + exit(1); + } + + // Reading of the current camera settings stored in the user profile + GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, + UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, + UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, + &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageCameraSize, &Error); + + if(!Error) + { + printf("\n**Camera settings** \n"); + printf ("PhasicsS/N=%s\n",SNPhasics); + printf ("FrameRate=%d\n",CameraInformation.FrameRate); + printf ("Gain=%d\n",CameraInformation.Gain); + printf ("ExposureTime=%d\n",CameraInformation.ExposureTime); + printf ("TriggerMode=%d\n",CameraInformation.TriggerMode); + } + + + nrow = ImageCameraSize.height; // 480 + ncol = ImageCameraSize.width; // 640 + + // Memory allocation of Image before calling GrabImage function + long Image_bufSize = nrow*ncol; + short int *Image = (short int*)malloc(sizeof(short int)* Image_bufSize); + + // Grab an image from camera + GrabImage(&SessionID, Image, Image_bufSize, &ImageCameraSize, &Error); + + if(!Error) + { printf("\n**Image content**\n"); + printf ("Image[0,0]=%d ",Image[0]); + } + + // Close the SID4 session + CloseSID4(&SessionID,&Error); + + // Release memory + free(Image); +} diff --git a/microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp b/microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp new file mode 100644 index 00000000..3c8af35b --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp @@ -0,0 +1,31 @@ +/* + © Copyright 2008 by PHASICS. + All rights reserved. + + @File: SID4_SDK_OpenSID4_Example.cpp + @Description: This example shows how to use OpenSID4() and CloseSID4() functions. + + OpenSID4 loads the configuration file specified by "userProfile_File" in memory. + It returns a unique reference ID "SessionID" that should be used as an input + for all other SID4 SDK functions. + + CloseSID4 closes the SID4 session. It releases memory devoted to the session. +*/ + +#include +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +void main(void) +{ + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + SDK_Reference SessionID=0; + long Error=0; + + OpenSID4(userProfile_File,&SessionID,&Error); + printf ("This example shows how to use OpenSID4() and CloseSID4() functions\n"); + printf ("SessionID=%d; Error=%d",SessionID,Error); + CloseSID4(&SessionID,&Error); + //getchar(); + printf("ok"); +} diff --git a/microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp b/microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp new file mode 100644 index 00000000..abba5aca --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp @@ -0,0 +1,137 @@ +/* + © Copyright 2008 by PHASICS. + All rights reserved. + + @File: SID4_SDK_SaveMeasurement_Example.cpp + @Description: This example shows how to save the Phase and Intensity maps. + + This example involves 5 SID4 SDK's functions: + + "OpenSID4" loads the configuration file specified by "userProfile_File" in memory. + It returns a unique reference ID "SessionID" that should be used as an input + for all other SID4 SDK functions. + + "GetUserProfile" outputs the parameters currently used to analyse analyse interferograms + and the camera settings. + + FileAnalysis analyses the interferogram. It ouputs a Phase and Intensity map, Tilt information + (X and Y tilts) removed from the ouput phase. + + "SaveMeasurement" saves the Phase and Intensity maps (2D single precision real array). + The filenames used are derived from the generic file name. The function adds a prefix "PHA" + for the phase file and a "INT" prfix for the intensity file. The arrays are saved in TIFF format. + The normalization information for the TIFF files are saved in a companion file, which prefix is "ACC". + + CloseSID4 closes the SID4 session. It releases memory devoted to the session. +*/ + + +#include +#include +#include // for memory allocation +#include "SID4_SDk.h" +#include "SID4_SDk_Constants.h" + +const int bufSize=1024; + + +void main(void) +{ + char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; + char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; + char GenericPath[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Result\\Interfo"; + SDK_Reference SessionID=0; + int nrow, ncol; + long Error=0; + char UserProfile_Name[bufSize]=""; + long uspName_bufSize = bufSize; + char UserProfile_File[bufSize]=""; + long uspFile_bufSize = bufSize; + char UserProfile_Description[bufSize]=""; + long uspDesc_bufSize = bufSize; + char UsrP_LastReference[bufSize]=""; + long uspLastRef_bufSize = bufSize; + char UserProfile_Directory[bufSize]=""; + long uspDir_bufSize = bufSize; + char SDKVersion[bufSize]=""; + long version_bufSize = bufSize; + AnalysisInfo AnalysisInformation; + CameraInfo CameraInformation; + char SNPhasics[bufSize]=""; + long SNPhasics_bufSize = bufSize; + ArraySize ImageSize; + + // Open SID4 Session + OpenSID4(userProfile_File,&SessionID,&Error); + if(!Error) + { printf ("************************ SID4 Session correctly opened **********************\n"); + printf ("SessionID=%d; Error=%d",SessionID,Error); + } + else{ + printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); + exit(1); + } + + // Reading of parameters currently used for interferogram analysis + GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, + UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, + UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, + &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); + + // Array dimension for Phase and Intensity + nrow = AnalysisInformation.PhaseSize_Height; + ncol = AnalysisInformation.PhaseSize_width; + + //// memory allocation for Phase and Intensity before calling FileAnalysis + TiltInfo TiltInformation; + long Intensity_bufSize = nrow*ncol; + long Phase_bufSize = nrow*ncol; + ArraySize AnalysisArraySize; + + AnalysisArraySize.height=nrow; + AnalysisArraySize.width=ncol; + + float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); + float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); + + + // Interferogram Analysis. We get in output Phase and Intensity map, tiltInformation + FileAnalysis(&SessionID, &AnalysisArraySize, inteferogram_File, Intensity, Intensity_bufSize, + Phase, Phase_bufSize, &TiltInformation, &Error); + if(!Error) + { printf ("\nXtilt=%f; Ytilt=%f\n",TiltInformation.XTilt,TiltInformation.YTilt); + } + else{ + printf ("\nThe error %d occured in the FileAnalysis function!\n\n",Error); + exit(1); + } + + // Save the Phase and Intensity maps. The function returns the Intensity and Phase path files. + char PhaseFileOut[bufSize]=""; + long phaseFileOut_bufSize = bufSize; + char IntensityFileOut[bufSize]=""; + long intensityFileOut_bufSize = bufSize; + + SaveMeasurement(&SessionID, GenericPath, &AnalysisArraySize, Phase, Phase_bufSize, Intensity, + Intensity_bufSize, PhaseFileOut, phaseFileOut_bufSize, + IntensityFileOut, intensityFileOut_bufSize, &Error); + if(!Error) + { printf ("\nThe intensity map has been saved in the following file :\n"); + printf ("\n%s\n", IntensityFileOut); + printf ("\nThe phase map has been saved in the following file :\n"); + printf ("\n%s\n", PhaseFileOut); + } + else{ + printf ("\nThe error %d occured in the SaveMeasurement function!\n\n",Error); + exit(1); + } + + + // Close the SID4 session + CloseSID4(&SessionID,&Error); + + // Memory release + free(Intensity); + free(Phase); + +} diff --git a/microscope/wavefront_sensors/camera.py b/microscope/wavefront_sensors/camera.py new file mode 100644 index 00000000..6d837642 --- /dev/null +++ b/microscope/wavefront_sensors/camera.py @@ -0,0 +1,577 @@ +# This is Eric Branlund's code for the Zyla camera + +import memhandler +import neo + +import numpy +import numpy.ctypeslib +import Pyro4 + +import ctypes +import gc +import Queue +import threading +import time +import traceback + +## Needed to keep the daemon from only listening to requests originating +# from the local host. +MY_IP_ADDRESS = '10.0.0.2' + +## Cropping modes +(CROP_FULL, CROP_HALF, CROP_512, CROP_256, CROP_128) = range(5) + +## Trigger modes +(TRIGGER_INTERNAL, TRIGGER_EXTERNAL, TRIGGER_EXTERNAL_EXPOSURE) = range(3) + +STATIC_BLACK = numpy.zeros((512, 512), dtype = numpy.uint16) + +## Save an array as an image. Copied from +# http://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image +# Mostly this just makes it easier to view images for debugging. The image +# uses false color and thus isn't really useful for actual work. +def imsave(filename, array, vmin=None, vmax=None, cmap=None, + format=None, origin=None): + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + from matplotlib.figure import Figure + + fig = Figure(figsize=array.shape[::-1], dpi=1, frameon=False) + canvas = FigureCanvas(fig) + fig.figimage(array, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin) + fig.savefig(filename, dpi=1, format=format) + + +## Because all functions in the neo module a) accept a camera handle as a +# first argument, and b) return an error code as the primary return value, +# we make this wrapper around the entire library to make interacting with +# it cleaner. It initializes the library, connects to the camera, and +# wraps every API function to handle error conditions. +# We also handle a couple of other functions through the memhandler.dll +# library via ctypes. They behave broadly similarly. +class WrappedAndor: + def __init__(self): + self.errorCodes = dict() + for key, value in neo.__dict__.iteritems(): + if callable(value): + self.__dict__[key] = self.wrapFunction(value) + # Also capture the error codes at this time so we can + # provide their names instead of bare numbers. + elif 'AT_ERR' == key[:6] or 'ERR_' == key[:4]: + self.errorCodes[value] = key + + ## Loaded object for memhandler.dll. + self.memLib = self.initMemhandler() + + startTime = time.time() + print "Initializing Andor library...", + error = neo.AT_InitialiseLibrary() + if error: + raise RuntimeException("Failed to initialize Andor library: %s" % self.errorCodes[error]) + print "done in %.2f seconds" % (time.time() - startTime) + error, numDevices = neo.AT_GetInt(neo.AT_HANDLE_SYSTEM, "DeviceCount") + if error: + raise RuntimeError("Failed to get number of devices: %s" % self.errorCodes[error]) + print "There are %d connected devices" % numDevices + error, self.handle = neo.AT_Open(0) + if error: + raise RuntimeError("Failed to connect to camera: %s" % self.errorCodes[error]) + elif self.handle == -1: + raise RuntimeError("Got an invalid handle from the camera") + else: + print "Connected to camera with handle",self.handle + + + ## Clean up after ourselves. + def __del__(self): + print "Close:",neo.AT_Close(self.handle) + print "Finalize:",neo.AT_FinaliseLibrary() + + + ## For the high-throughput functions AT_QueueBuffer and AT_WaitBuffer, we use + # ctypes instead of SWIG, in an attempt to avoid weird throughput issues we + # have otherwise. + def initMemhandler(self): + memLib = ctypes.CDLL('memhandler.dll') + # Args are handle, numBuffers, numElements + memLib.allocMemory.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int) + # Returns an error code. + memLib.allocMemory.restype = ctypes.c_int + # Construct the return type that's a point to a buffer of memory. + bufType = numpy.ctypeslib.ndpointer(dtype = numpy.uint16, ndim = 1, + flags = ('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE')) + # Args are output buffer, timeout + memLib.getImage.argtypes = (bufType, ctypes.c_int, ctypes.c_double) + # Returns an error code. + memLib.getImage.restype = ctypes.c_int + return memLib + + + ## Manual wrapper around the memhandler.allocMemory() function. + def allocMemory(self, numBuffers, numElements): + return self.processReturn(self.memLib.allocMemory( + self.handle, numBuffers, numElements)) + + + ## Manual wrapper around the memhandler.getImage() function. + def getImage(self, numElements, timeout): + imageBuffer = numpy.ndarray(numElements, dtype = numpy.uint16, + order = 'C') + # Multiply by 2 because there's 2 bytes per uint16. + error = self.memLib.getImage(imageBuffer, numElements * 2, .5) + if error: + raise RuntimeError("getImage failed") + return imageBuffer + + + ## Manual decorator function -- call the passed-in function with our + # handle, and raise an exception if an error occurs. + def wrapFunction(self, func): + def wrappedFunction(*args, **kwargs): + result = func(self.handle, *args, **kwargs) + return self.processReturn(result) + return wrappedFunction + + + ## Handle function return values. + # result may be a single value, a length-2 list, or a + # length-3+ list. We return None, the second value, or + # a tuple in those respective cases. + def processReturn(self, result): + errorCode = result + returnVal = None + if type(result) in [tuple, list]: # Type paranoia + errorCode = result[0] + if len(result) == 2: + returnVal = result[1] + else: + returnVal = tuple(result[1:]) + if errorCode: + errorString = "unknown error %s" % errorCode + if errorCode in self.errorCodes: + errorString = "error %s" % self.errorCodes[errorCode] + raise RuntimeError("An %s occurred." % errorString) + return returnVal + + + +wrappedAndor = WrappedAndor() + + + + +## Decorator function to put the camera in a mode where it can be +# interacted with. Mostly needed to stop acquisitions. +def resetCam(func): + def wrappedFunc(*args, **kwargs): + if wrappedAndor.AT_GetBool('CameraAcquiring'): + wrappedAndor.AT_Command('AcquisitionStop') + wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') + wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') + wrappedAndor.AT_SetInt('FrameCount', 1) + func(*args, **kwargs) + return wrappedFunc + + +## This class exposes various Andor library functions to outside clients, +# and handles collecting and transferring data. +class Camera: + def __init__(self): + ## Cached copy of the sensor width, since we need to access this + # regularly. + self.width = wrappedAndor.AT_GetInt('SensorWidth') + ## See self.width. + self.height = wrappedAndor.AT_GetInt('SensorHeight') + ## Current crop mode (e.g. CROP_FULL, CROP_512, etc.) + self.curCropMode = CROP_FULL + + print "Firmware version:",wrappedAndor.AT_GetString('FirmwareVersion') + print "Camera serial number:",wrappedAndor.AT_GetString('SerialNumber') + print "CCD sensor shape:",self.width,self.height + print "Bit depth:",wrappedAndor.AT_GetEnumIndex('BitDepth') + print "Pixel encoding:",wrappedAndor.AT_GetEnumIndex('PixelEncoding') + print "Shutter mode:",wrappedAndor.AT_GetEnumIndex('ElectronicShutteringMode') + print "Fan speed:",wrappedAndor.AT_GetEnumIndex('FanSpeed') + print "Sensor cooling:",wrappedAndor.AT_GetBool('SensorCooling') + print "Temp status:",wrappedAndor.AT_GetEnumIndex('TemperatureStatus') + print "Sensor temperature",self.getSensorTemperature() + print "Baseline level:",wrappedAndor.AT_GetInt('BaselineLevel') + + wrappedAndor.AT_SetEnumString("FanSpeed", "Off") + wrappedAndor.AT_SetBool("SensorCooling", True) + print "Pre-amp gain options:" + for i in xrange(wrappedAndor.AT_GetEnumCount("SimplePreAmpGainControl")): + print "%d:" % i, wrappedAndor.AT_GetEnumStringByIndex("SimplePreAmpGainControl", i) + wrappedAndor.AT_SetEnumString("SimplePreAmpGainControl", "16-bit (low noise & high well capacity)") + wrappedAndor.AT_SetEnumString("PixelReadoutRate", "280 MHz") + + self.setExposureTime(.1) + + self.dataThread = DataThread(self, self.width, self.height) + + self.setCrop(CROP_512) + self.setShutterMode(True) + self.dataThread.start() + + + ## Get told who we should be sending image data to, and how we + # should mark ourselves when we do. + def receiveClient(self, uri): + print "Receiving new client",uri + if uri is None: + self.dataThread.setClient(None) + self.dataThread.shouldSendImages = False + else: + connection = Pyro4.Proxy(uri) + connection._pyroTimeout = 5 + self.dataThread.setClient(connection) + self.dataThread.shouldSendImages = True + + + ## Stop sending images to the client, even if we still + # receive them. + def goSilent(self): + print "Switching to quiet mode" + self.dataThread.shouldSendImages = False + + + ## Start sending new images to the client again. + def goLoud(self): + print "Switching to loud mode" + self.dataThread.shouldSendImages = True + + + ## Stop acquiring images. + def abort(self): + wrappedAndor.AT_Command("AcquisitionStop") + + + ## Retrieve the specified number of images. This may fail + # if we ask for more images than we have, so the caller + # should be prepared to catch exceptions. + def getImages(self, numImages): + result = [] + for i in xrange(numImages): + image = self.extractOneImage() + result.append(image) + return result + + + ## Discard the specified number of images from the queue, + # defaulting to all of them. + def discardImages(self, numImages = None): + count = 0 + while count != numImages: + try: + self.extractOneImage() + count += 1 + except Exception, e: + # Got an exception, probably because the + # queue is empty, so we're all done here. + return + + + ## Set the exposure time, in seconds. + @resetCam + def setExposureTime(self, seconds = .01): + print "Set exposure time to", seconds, "seconds" + wrappedAndor.AT_SetFloat('ExposureTime', seconds) + + + ## Simple getter. + def getExposureTime(self): + return wrappedAndor.AT_GetFloat('ExposureTime') + + + ## Set the cropping mode to one of a few presets. In version + # 2 of the SDK, these were the only valid crop modes; in + # version 3, we can set any crop mode we want, but these + # presets are still convenient. + def setCrop(self, mode): + self.curCropMode = mode + width = [2560, 1392, 540, 240, 144][mode] + left = [1, 601, 1033, 1177, 1225][mode] + height = [2160, 1040, 512, 256, 128][mode] + top = [1, 561, 825, 953, 1017][mode] + self.setCropArbitrary(left, top, width, height) + + + ## Get the current crop mode. + def getCropMode(self): + return self.curCropMode + + + ## Set our cropping to an arbitrary region of interest. + def setCropArbitrary(self, left, top, width, height): + wrappedAndor.AT_SetInt('AOIWidth', width) + wrappedAndor.AT_SetInt('AOILeft', left) + wrappedAndor.AT_SetInt('AOIHeight', height) + wrappedAndor.AT_SetInt('AOITop', top) + + self.width = width + self.height = height + self.dataThread.setImageDimensions(width, height) + + # Reset the memory used to transfer images from the camera. + # We allocate about 500MB of RAM to the image buffer. Allocating + # too much memory seems to cause slowdowns and crashes, oddly enough. + imageBytes = wrappedAndor.AT_GetInt('ImageSizeBytes') + numImages = (500 * 1024 * 1024) / imageBytes + print "Allocating",numImages,"images at",imageBytes,"bytes per image" + wrappedAndor.allocMemory(numImages, imageBytes) + + stride = wrappedAndor.AT_GetInt('AOIStride') + div = float(stride) / width + print "Got stride",stride,"compare",width,"giving div",div + return (stride, width, int(div) == div) + + + ## Set an offset correction image to use. + def setOffsetCorrection(self, image): + self.dataThread.setOffsetCorrection(image) + + + ## Return true if a correction file is loaded + # for the current image dimensions. + def getIsOffsetCorrectionOn(self): + correction = self.dataThread.getOffsetCorrection() + return correction is not None and correction.shape == (self.height, self.width) + + + ## Retrieve the current sensor temperature in degrees Celsius. + def getSensorTemperature(self): + return wrappedAndor.AT_GetFloat('SensorTemperature') + + + ## Get the shape of the images we generate + def getImageShape(self): + return (self.width, self.height) + + + ## Set the trigger mode. + @resetCam + def setTrigger(self, triggerMode): + wrappedAndor.AT_SetEnumString('CycleMode', 'Continuous') + modeString = '' + if triggerMode == TRIGGER_INTERNAL: + wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') + modeString = 'internal trigger' + elif triggerMode == TRIGGER_EXTERNAL: + wrappedAndor.AT_SetEnumString('TriggerMode', 'External') + wrappedAndor.AT_Command('AcquisitionStart') + modeString = 'external trigger' + elif triggerMode == TRIGGER_EXTERNAL_EXPOSURE: + wrappedAndor.AT_SetEnumString('TriggerMode', 'External Exposure') + wrappedAndor.AT_Command('AcquisitionStart') + modeString = 'external exposure' + print "Set trigger mode to",modeString + + + ## Set the shutter mode. + def setShutterMode(self, isGlobal): + # 0 is rolling shutter; 1 is global. + wrappedAndor.AT_SetEnumIndex('ElectronicShutteringMode', int(isGlobal)) + + + ## Get the current shutter mode. + def getIsShutterModeGlobal(self): + return wrappedAndor.AT_GetEnumIndex('ElectronicShutteringMode') == 1 + + + ## Get the time needed to read out the image. + def getReadoutTime(self): + return wrappedAndor.AT_GetFloat('ReadoutTime') + + + ## Below this point lie debugging functions. + + + ## Acquire some number of images with internal trigger. + def triggerInternally(self, imageCount, exposureTime): + wrappedAndor.AT_Command('AcquisitionStop') + print "Acquiring %d images with exposure time %d" % (imageCount, exposureTime) + wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') + wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') + wrappedAndor.AT_SetInt('FrameCount', imageCount) + wrappedAndor.AT_SetFloat('ExposureTime', exposureTime / 1000.0) + # Hardcoded for now. + wrappedAndor.AT_SetFloat('FrameRate', 125) + wrappedAndor.AT_Command("AcquisitionStart") + + + ## Generate synthetic images at the specified framerate (in FPS). + def generateSequence(self, imageCount, frameRate): + threading.Thread(target = self.generateSequence2, args = [imageCount, frameRate]).start() + + + ## As above, but in a new thread. + def generateSequence2(self, imageCount, frameRate): + waitTime = 1 / float(frameRate) + image = numpy.zeros((self.width, self.height), dtype = numpy.uint16) + for i in xrange(imageCount): + curTime = time.clock() + self.dataThread.imageQueue.put((image, curTime)) + nextTime = time.clock() + putDelta = nextTime - curTime + if putDelta > waitTime * 1.25: + print "Putting is slow!",putDelta,waitTime + time.sleep(waitTime) + finalTime = time.clock() + if finalTime - nextTime > max(.02, waitTime * 1.25): + print "Sleeping is slow!",(finalTime - nextTime), waitTime + + + ## Set the gain mode as an index into the enums. Again, you generally + # shouldn't need to call this from outside. + def setGainEnum(self, val): + wrappedAndor.AT_SetEnumIndex('SimplePreAmpGainControl', val) + + + ## Set the readout rate as an index into the enums. Also for debugging. + def setReadoutEnum(self, val): + wrappedAndor.AT_SetEnumIndex('PixelReadoutRate', val) + + + ## Set the frame count to a different value and go into external + # trigger mode. Just for debugging. + def setFrameCount(self, val): + if wrappedAndor.AT_GetBool('CameraAcquiring'): + wrappedAndor.AT_Command('AcquisitionStop') + wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') + wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') + wrappedAndor.AT_SetInt('FrameCount', val) + wrappedAndor.AT_SetEnumString('TriggerMode', 'External') + wrappedAndor.AT_SetEnumString('CycleMode', 'Continuous') + wrappedAndor.AT_Command('AcquisitionStart') + + + +## This class retrieves images from the camera, and sends them to our +# client. +class DataThread(threading.Thread): + def __init__(self, parent, width, height): + threading.Thread.__init__(self) + + ## Loop back to parent to be able to communicate with it. + self.parent = parent + + ## Image dimensions, which we need for when we retrieve image + # data. Our parent is responsible for updating these for us + # via setImageDimensions(). + self.width = self.height = 0 + ## Lock on modifying the above. + self.sensorLock = threading.Lock() + + ## Connection to client + self.clientConnection = None + + ## Whether or not we should unload images from the camera + self.shouldSendImages = True + + ## Initial timestamp that we will use in conjunction with time.clock() + # to generate high-time-resolution timestamps. Just using time.time() + # straight-up on Windows only has accuracy of ~15ms. + self.initialTimestamp = time.time() + time.clock() + + ## Offset image array to subtract off of each image we + # receive. + self.offsetImage = None + + + ## Pull images from self.imageQueue and send them to the client. + def run(self): + count = 0 + gTime = None + getTime = 0 + fixTime = 0 + sendTime = 0 + while True: + # This will block indefinitely until images are available. + with self.sensorLock: + try: + start = time.clock() + image = wrappedAndor.getImage(self.width * self.height, .5) + getTime += (time.clock() - start) + except Exception, e: + if 'getImage failed' not in e: + print "Error in getImage:",e + # Probably a timeout; just try again. + continue + # \todo This timestamp is potentially bogus if we get behind in + # processing images. + timestamp = time.clock() + self.initialTimestamp +# print "Image has shape",image.shape,"min/max",image.min(),image.max() + start = time.clock() + image = self.fixImage(image) + fixTime += time.clock() - start + count += 1 + if count % 100 == 0: + # Periodically manually invoke the garbage collector, to + # ensure that we don't build up a giant pile of work that + # would interfere with our average write speed. + if gTime is None: + gTime = time.time() + delta = time.time() - gTime + print count, delta, getTime, fixTime, sendTime + gTime = time.time() + getTime = fixTime = sendTime = 0 + gc.collect() + + if self.shouldSendImages and self.clientConnection is not None: + try: + start = time.clock() + self.clientConnection.receiveData('new image', image, timestamp) + cost = time.clock() - start + if cost > .5: + print "Took %.2fs to send to client" % cost + sendTime += cost + except Exception, e: + print "Failed to send image to client: %s", e + traceback.print_exc() + + + ## Fix an image -- set its shape and apply any relevant correction. + def fixImage(self, image): + image.shape = self.height, self.width + if self.offsetImage is not None and self.offsetImage.shape == image.shape: + # Apply offset correction. + image -= self.offsetImage + return image + + + ## Update who we send image data to. + def setClient(self, connection): + self.clientConnection = connection + + + ## Update our image dimensions. + def setImageDimensions(self, width, height): + with self.sensorLock: + self.width = width + self.height = height + + + ## Update the image we use for offset correction. + def setOffsetCorrection(self, image): + self.offsetImage = image + + + ## Retrieve our offset correction image. + def getOffsetCorrection(self): + return self.offsetImage + + + +try: + cam = Camera() + daemon = Pyro4.Daemon(port = 7000, host = MY_IP_ADDRESS) + Pyro4.Daemon.serveSimple( + { + cam: 'Andorcam', + }, + daemon = daemon, ns = False, verbose = True + ) + +except Exception, e: + traceback.print_exc() + +del wrappedAndor # Clean up after ourselves. diff --git a/microscope/wavefront_sensors/memoryHandler.py b/microscope/wavefront_sensors/memoryHandler.py new file mode 100644 index 00000000..c4a46297 --- /dev/null +++ b/microscope/wavefront_sensors/memoryHandler.py @@ -0,0 +1,185 @@ +''' +This program is intended to provide a pure-C "client" for interacting with +the camera, for use in debugging issues we've been having with running +long, high-speed acquisitions. It's basically a copy of the memhandler +code with some preambles and code to actually deal with the image data. +''' + +import AndorSDK3 as SDK3 +import threading +import Queue +import numpy as np +import ctypes +from time import sleep +from numpy import dtype + +class MemoryHandler(): + ''' + MemoryHandler doc + ''' + def __init__(self, handle): + self.handle = handle + self.buffersQueue = Queue.Queue() # Queue of buffers where images will be stored + self.imageBytes = 0 # Number of bytes used by one image + self.imagesQueue = Queue.Queue() # Queue of images waiting to be consumed + self.imageQueueLock = threading.Lock() # a lock for the image queue + self.readThread = threading.Thread() # initialize an empty thread. This one will be replaced as soon as first memory is allocated + self.readThreadLock = threading.Lock() # a lock for the readThread + self.shouldReadImages = False + + def stop(self): + ''' + Stops any active threads + ''' + # Halt any active read thread + if self.readThread.isAlive(): + with self.readThreadLock: + self.shouldReadImages = False + self.readThread.join() + + # Wipe the list of images waiting to be read out + with self.imageQueueLock: + while not self.imagesQueue.empty(): + self.imagesQueue.get() + + # Flush the camera buffer and wipe the buffersQueue, if it exists + if not self.buffersQueue.empty(): + try: + SDK3.Flush(self.handle) + except SDK3.CameraError as e: + print(e) + return 1 + while not self.buffersQueue.empty(): + self.buffersQueue.get() + + + def allocMemory(self, numBuffers, imageBytes, imageWidth, imageHeight, strides, timeout, dtype = ' Date: Tue, 17 Oct 2017 16:53:56 +0200 Subject: [PATCH 03/27] SID4 initializes. Cannot manage to get camera attributes programmatically --- microscope/devices.py | 32 +- microscope/wavefront_sensors/ConsoleTests.py | 297 +---- .../wavefront_sensors/SID4_SDK-cffiAPI.py | 318 +++++ microscope/wavefront_sensors/SID4_SDK.py | 1133 ++++++++++++----- microscope/wavefront_sensors/SID4_SDK_cffi.py | 225 ++++ 5 files changed, 1392 insertions(+), 613 deletions(-) create mode 100644 microscope/wavefront_sensors/SID4_SDK-cffiAPI.py create mode 100644 microscope/wavefront_sensors/SID4_SDK_cffi.py diff --git a/microscope/devices.py b/microscope/devices.py index 72e35b4d..1ec2e08f 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -781,24 +781,16 @@ class WavefrontSensorDevice(CameraDevice): def __init__(self, *args, **kwargs): super(WavefrontSensorDevice, self).__init__(**kwargs) # Add WFS settings - # Zernike indexes to be returned + # list of Zernike indexes to be returned self._zernike_indexes = [] - self.add_setting('zernike_indexes', 'int', - self.get_zernike_indexes, - self.set_zernike_indexes, - 'list of integers')) # Filter the phase map returned by a number of the Zernike measurements self._zernike_phase_filter = [] - self.add_setting('zernike_phase_filter', 'bool', - self.get_zernike_phase_filter, - self.set_zernike_phase_filter, - 'list of integers') # WaveLength at which the phase map is calculated - self._wavelength = None - self.add_setting('wavelength', 'int', - self.get_wavelength, - self.set_wavelength, - 'wavelength in nm') + self._wavelength_nm = None + self.add_setting('wavelength_nm', 'float', + self._get_wavelength_nm, + self._set_wavelength_nm, + (300.0, 2000,0)) # Some acquisition related methods # @@ -960,11 +952,11 @@ def set_zernike_phase_filter(self, phase_filter): self._zernike_phase_filter = phase_filter @Pyro4.expose - def get_wavelength(self): - return self._wavelength + def get_wavelength_nm(self): + return self._wavelength_nm @abc.abstractmethod - def _set_wavelength(self, wavelength): + def _set_wavelength_nm(self, wavelength): """Sets the wavelength that is used for tha phase calculation :param wavelength: integer representing the wavelength in nm. @@ -973,7 +965,7 @@ def _set_wavelength(self, wavelength): pass @Pyro4.expose - def set_wavelength(self, wavelength): - if self._set_wavelength(wavelength): - self._wavelength = wavelength + def set_wavelength_nm(self, wavelength): + if self._set_wavelength_nm(wavelength): + self._wavelength_nm = wavelength diff --git a/microscope/wavefront_sensors/ConsoleTests.py b/microscope/wavefront_sensors/ConsoleTests.py index 19247c13..38ddcc68 100644 --- a/microscope/wavefront_sensors/ConsoleTests.py +++ b/microscope/wavefront_sensors/ConsoleTests.py @@ -1,244 +1,63 @@ -Python -2.7 -.13(v2 -.7 -.13: a06454b1afa1, Dec -17 -2016, 20: 42:59) [MSC v.1500 32 bit(Intel)] -on -win32 -Type -"copyright", "credits" or "license()" -for more information. - >> > import os, ctypes ->> > os.chdir('C:\Program Files (x86)\SID4_SDK\DLL SDK\BIN') ->> > SDK = ctypes.WinDLL('SID4_SDK') ->> > ->> > SDK -< WinDLL -'SID4_SDK', handle -3110000 -at -29 -b58f0 > ->> > UserProfileFile = 'C:\Program Files (x86)\SID4\phasics\SID4-079b default profile.txt' ->> > SDK.OpenSID4(UserProfileFile) -0 ->> > r = SDK.OpenSID4(UserProfileFile) ->> > r -0 ->> > OpenSID4 = getattr(SDK, 'OpenSID4') ->> > OpenSID4 -< _FuncPtr -object -at -0x02861648 > ->> > OpenSID4.restype -< class 'ctypes.c_long'> - ->> > OpenSID4.argtypes ->> > r -0 ->> > type(r) -< type -'int' > ->> > reference = ctypes.c_int ->> > refPointer = ctypes.POINTER(reference) ->> > refPointer -< class '__main__.LP_c_long'> - ->> > p = refPointer() ->> > p -< __main__.LP_c_long -object -at -0x02886AD0 > ->> > e = refPointer() ->> > e -< __main__.LP_c_long -object -at -0x02833F30 > ->> > e -< __main__.LP_c_long -object -at -0x02833F30 > ->> > OpenSID4 -< _FuncPtr -object -at -0x02861648 > ->> > OpenSID4(UserProfileFile, p, e) - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -OpenSID4(UserProfileFile, p, e) -ValueError: Procedure -probably -called -with too many arguments (12 bytes in excess) ->> > OpenSID4(p, e) - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -OpenSID4(p, e) -ValueError: Procedure -probably -called -with too many arguments (8 bytes in excess) ->> > OpenSID4() - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -OpenSID4() -WindowsError: exception: access -violation -writing -0x7332D7A6 ->> > OpenSID4(p) -0 ->> > OpenSID4(e) -0 ->> > e -< __main__.LP_c_long -object -at -0x02833F30 > ->> > p -< __main__.LP_c_long -object -at -0x02886AD0 > ->> > OpenSID4.argtypes ->> > OpenSID4 = getattr(SDK, 'OpenSID4') ->> > OpenSID4 -< _FuncPtr -object -at -0x02861648 > ->> > -KeyboardInterrupt ->> > class SDK_Reference(ctypes.Structure): - _fields_ = [('SDK_Reference', c_int)] - - -Traceback(most -recent -call -last): -File -"", line -1, in < module > +import os +import ctypes +UserProfileFile = 'C:\Program Files (x86)\SID4\phasics\SID4-079b default profile.txt' +SDK = ctypes.CDLL('SID4_SDK') +UserProfileLocation = ctypes.c_char_p(UserProfileFile) +class SDK_Reference(ctypes.Structure): + _fields_ = [('SDK_Reference', ctypes.c_int)] +SessionID = SDK_Reference() +SessionIDRef = ctypes.byref(SessionID) +ErrorCode = ctypes.c_long() +ErrorCodeRef = ctypes.byref(ErrorCode) -class SDK_Reference(ctypes.Structure): +Phase = ctypes.c_float() +PhaseRef = ctypes.byref(Phase) - File -"", line -2, in SDK_Reference -_fields_ = [('SDK_Reference', c_int)] -NameError: name -'c_int' is not defined ->> > class SDK_Reference(ctypes.Structure): - _fields_ = [('SDK_Reference', ctypes.c_int)] +print('Opening SDK...') +SDK.OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) + +print(ErrorCode) + +print('Session is:') +print(SessionID.SDK_Reference) + +# print('Initializing camera...') +# SDK.CameraInit(SessionIDRef, ErrorCodeRef) +# +# print(ErrorCode) +# +# print('Starting Camera...') +# SDK.CameraStart(SessionIDRef, ErrorCodeRef) +# +# print(ErrorCode) + +print('Starting Camera LifeMode...') +SDK.StartLiveMode(SessionIDRef, ErrorCodeRef) + +print(ErrorCode) + +####### + +####### +print('Stopping Camera LifeMode...') +SDK.StopLiveMode(SessionIDRef, ErrorCodeRef) + +print(ErrorCode) + +print('Stopping Camera...') +SDK.CameraStop(SessionIDRef, ErrorCodeRef) + +print(ErrorCode) + +print('Closing Camera...') +SDK.CameraClose(SessionIDRef, ErrorCodeRef) + +print(ErrorCode) + +print('Closing SDK...') +SDK.CloseSID4(SessionIDRef, ErrorCodeRef) + +print(ErrorCode) ->> > UserProfileFile -'C:\\Program Files (x86)\\SID4\\phasics\\SID4-079b default profile.txt' ->> > UserProfileLocation = c_char_p(UserProfileFile) - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -UserProfileLocation = c_char_p(UserProfileFile) -NameError: name -'c_char_p' is not defined ->> > UserProfileLocation = ctypes.c_char_p(UserProfileFile) ->> > UserProfileLocation -c_char_p('C:\\Program Files (x86)\\SID4\\phasics\\SID4-079b default profile.txt') ->> > ErrorCode = ctypes.c_long() ->> > ErrorCodeRef = ctypes.byref(ErrorCode) ->> > ErrorCodeRef -< cparam -'P'(02 -99 -CC88) > ->> > SessionID = SDK_Reference() ->> > SessionIDRef = ctypes.byref(SessionID) ->> > OpenSID4(UserProfileLocation, SessioIDRef, ErrorCodeRef) - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -OpenSID4(UserProfileLocation, SessioIDRef, ErrorCodeRef) -NameError: name -'SessioIDRef' is not defined ->> > OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) -ValueError: Procedure -probably -called -with too many arguments (12 bytes in excess) ->> > SDK1 = ctypes.CDLL('SID4_SDK') ->> > SDK1.OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) -0 ->> > SessionID -< __main__.SDK_Reference -object -at -0x0299CCB0 > ->> > SessionID.value - -Traceback(most -recent -call -last): -File -"", line -1, in < module > -SessionID.value -AttributeError: 'SDK_Reference' -object -has -no -attribute -'value' ->> > SessionID -< __main__.SDK_Reference -object -at -0x0299CCB0 > ->> > ErrorCode -c_long(7017) \ No newline at end of file diff --git a/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py b/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py new file mode 100644 index 00000000..01751033 --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 +# +# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""SID4_SDK wavefront sensor device. + +This class provides a wrapper for SID4's SDK interface that allows +a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. +""" + +from cffi import FFI + + +# We import the headers from a file in order to avoid copyright issues +header_path = 'C:\Program Files (x86)\SID4_SDK\DLL SDK\Headers\SID4_SDK.h' +dll_path = 'C:\Program Files (x86)\SID4_SDK\DLL SDK\BIN\SID4_SDK.dll' +cdef_from_file = '' + +try: + with open(header_path, 'r') as SDK_header: + cdef_from_file = SDK_header.read() +except FileNotFoundError: + print('Unable to find "%s" header file.' % header_path) + exit(1) +except IOError: + print('Unable to open "%s"' % header_path) + exit(2) +finally: + if cdef_from_file == '' or None: + print('File "%s" is empty' % header_path) + exit(3) + +ffibuilder = FFI() + +print(cdef_from_file[0:100]) + +ffibuilder.set_source("_SDK_SID4", + r""" // passed to the real C compiler + #include "C:\Program Files (x86)\SID4_SDK\DLL SDK\Headers\SID4_SDK.h" + """, + libraries=[]) # or a list of libraries to link with +# libraries=[dll_path]) + +# ''' +# Open VS prompt +# cd C:\Users\omxt\PycharmProjects\microscope\microscope\wavefront_sensors +# cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD^ +# -Ic:\Users\omxt\Applications\WinPython-32bit-3.6.1.0Qt5\python-3.6.1\include^ +# -Ic:\Users\omxt\Applications\WinPython-32bit-3.6.1.0Qt5\python-3.6.1\include^ +# "-IC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\ATLMFC\include"^ +# "-IC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\include"^ +# "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um"^ +# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\ucrt"^ +# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\shared"^ +# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\um"^ +# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\winrt"^ +# "-IC:\Program Files (x86)\SID4_SDK\DLL SDK\Headers"^ +# /Tc_SDK_SID4.c /Fo.\Release\_SDK_SID4.obj +# ''' + + +# ffibuilder.cdef(cdef_from_file) +ffibuilder.cdef(''' +#pragma pack(push) +#pragma pack(1) + +typedef unsigned char LVBoolean; + +typedef int SDK_Reference; + +//****************************************************************// +// Definitions of structures used in the SID4_SDK functions // +//****************************************************************// + +// Tilt Information +typedef struct { + float XTilt; + float YTilt; + } TiltInfo; + +// Size Information on the 2D arrays given as input parameters +typedef struct { + long nRow; + long nCol; + } ArraySize; + + +// Analysis Information, to be used with GetUserProfile +typedef struct { + double GratingPositionMm; + double wavelengthNm; + LVBoolean RemoveBackgroundImage; + long PhaseSize_width; + long PhaseSize_Height; + } AnalysisInfo; + +// Camera Information, to be used with GetUserProfile +typedef struct { + long FrameRate; + unsigned long TriggerMode; + long Gain; + unsigned long ExposureTime; + float PixelSizeM; + unsigned char NumberOfCameraRecorded; + } CameraInfo; + +//**************************************************************// +// SID4_SDK Basic functions // +//**************************************************************// + +// Configuration functions +void __cdecl OpenSID4(char UserProfileLocation[], SDK_Reference *SessionID, + long *ErrorCode); + +void __cdecl CloseSID4(SDK_Reference *SessionID, long *ErrorCode); + +void __cdecl GetUserProfile(SDK_Reference *SDKSessionID, char UserProfile_Name[], + long uspName_bufSize, char UserProfile_File[], long uspFile_bufSize, + char UserProfile_Description[], long uspDesc_bufSize, + char UsrP_LatestReference[], long uspLastRef_bufSize, + char UserProfile_Directory[], long uspDir_bufSize, char SDKVersion[], + long version_bufSize, AnalysisInfo *AnalysisInformation, CameraInfo *CameraInformation, + char SNPhasics[], long SNPhasics_bufSize, ArraySize *AnalysisArraySize, + long *ErrorCode); + +void __cdecl ChangeReference(SDK_Reference *SDKSessionID, char ReferencePath[], + unsigned short int ReferenceSource, char ArchivedPath[], long ArchivedPath_bufSize, + long *ErrorCode); + + void __cdecl SetBackground(SDK_Reference *SDKSessionID, unsigned short int Source, + char BackgroundFile[], char UpdatedBackgoundImageFile[], + long updatedImageFile_bufSize, long *ErrorCode); + +void __cdecl ChangeMask(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl LoadMaskDescriptorInfo(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl LoadMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl ModifyUserProfile(SDK_Reference *SDKSessionID, + AnalysisInfo *AnalysisInformation, unsigned short int ReferenceSource, char ReferencePath[], + char UserProfile_Description[], LVBoolean *ReferenceChanged, + long *ErrorCode); + +void __cdecl NewUserProfile(SDK_Reference *SDKSessionID, char CameraSNPhasics[], + char ProfileName[], char UserProfileDirectory[], char ProfilePathFileOut[], + long pathFileOut_bufSize, long *ErrorCode); + +void __cdecl SaveCurrentUserProfile(SDK_Reference *SDKSessionID, + long *ErrorCode); + +void __cdecl SaveMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +// Camera control functions +void __cdecl StartLiveMode(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl StopLiveMode(SDK_Reference *SDKSessionID, long *ErrorCodeID); + +void __cdecl CameraInit(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraStart(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraStop(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraClose(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraList(SDK_Reference *SDKSessionID, char CameraList_SNPhasics[], + long camList_bufSize, long *ErrorCode); + +void __cdecl CameraSetup(SDK_Reference *SDKSessionID, unsigned short int CameraParameter, + unsigned long Value, long *ErrorCode); + +void __cdecl Camera_ConvertExposureMs(SDK_Reference *SDKSessionID, + double ExposureRawValueIn, double *ExposureValueMsOut, long *ErrorCode); +void __cdecl Camera_GetNumberOfAttribute(SDK_Reference *SDKSessionID, + long *NumberOfAttribute, long *ErrorCode); + +void __cdecl Camera_GetAttribute(SDK_Reference *SDKSessionID, + unsigned short int AttributeID, double *AttributeValueOut, long *ErrorCode); +void __cdecl Camera_SetAttribute(SDK_Reference *SDKSessionID, + unsigned short int AttributeID, double *AttributeValue, long *ErrorCode); + +void __cdecl Camera_GetAttributeList(SDK_Reference *SDKSessionID, + unsigned short int AttributeID[], long attribID_bufSize, + char AttributeName_SeparatedByTab[], long attribName_bufSize, + long AttributeGmin[], long attribGmin_bufSize, long AttributeGmax[], + long attribGmax_bufSize, long *ErrorCode); + +// Interferogram analysis functions +void __cdecl ArrayAnalysis(SDK_Reference *SDKSessionID, + short int InterferogramInArrayI16[], long Interfero_bufSize, + float Intensity[], long Intensity_bufSize, float Phase[], + long Phase_bufSize, TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, + ArraySize *ImageCameraSize, long *ErrorCode); + +void __cdecl FileAnalysis(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, + char InterferogramFile[], float Intensity[], long Intensity_bufSize, + float Phase[], long Phase_bufSize, TiltInfo *TiltInformation, + long *ErrorCode); + +void __cdecl GrabLiveMode(SDK_Reference *SDKSessionID, float Phase[], + long Phase_bufSize, float Intensity[], long Intensity_bufSize, + TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl GrabImage(SDK_Reference *SDKSessionID, short int Image[], + long Image_bufSize, ArraySize *ImageCameraSize, long *ErrorCode); + +void __cdecl Snap(SDK_Reference *SDKSessionID, float Phase[], + long Phase_bufSize, float Intensity[], long Intensity_bufSize, + TiltInfo *TiltInformation, long *ErrorCode); + +void __cdecl GrabToFile(SDK_Reference *SDKSessionID, unsigned long PaletteNumber, + char InterferogramFile[], LVBoolean *CheckOverWrite, long *ErrorCode); + +void __cdecl GetPhaseGradients(SDK_Reference *SDKSessionID, + ArraySize *AnalysisArraySize, float GradientX[], long GradX_bufSize, + float GradientY[], long GradY_bufSize, long *ErrorCode); + +void __cdecl SetIntegrationParam(SDK_Reference *SDKSessionID, + unsigned char Adv_Activation, unsigned short int Adv_Niter, float Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl GetQualityMap(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, + float QualityMap[], long qualityMap_bufSize, long *ErrorCode); + +void __cdecl GetIntegrationParam(SDK_Reference *SDKSessionID, + unsigned char *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl SetUnwrapParam(SDK_Reference *SDKSessionID, + unsigned short int UnwrappingAlgorithm, unsigned char UnwrappingOptions[], + long unwrapOptions_bufSize, long *ErrorCode); + +void __cdecl GetUnwrapParam(SDK_Reference *SDKSessionID, + unsigned short int *UnwrappingAlgoritm, unsigned char UnwrappingOptions[], + long unwrapOptions_bufSize, long *ErrorCode); + +void __cdecl getIntegrationParamOut(SDK_Reference *SDKSessionID, + LVBoolean *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl ADVTR_GetAnalysisArraySize(SDK_Reference *SDKSessionID, + double TR_AnalyseIn, ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl ADVTR_ComputeAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, + short int InterferogramI16[], long interfero_bufSize, double *TR_AnalyseOut, + long *ErrorCode); + +void __cdecl ADVTR_ArrayAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, + short int InterferogramI16[], long interfero_bufSize, double TR_AnalyseIn, + ArraySize *AnalysisArraySize, float Phase[], long phase_bufSize, + float Intensity[], long intensity_bufSize, TiltInfo *TiltInformation, + long *ErrorCode); + +void __cdecl GetImageInfo(SDK_Reference *SDKSessionID, char InterferogramFile[], + ArraySize *ImageSize, long *ErrorCode); + + +// Input-Output functions +void __cdecl LoadInterferogram(SDK_Reference *SDKSessionID, + char InterferogramFile[], ArraySize *ImageSize, short int InterferogramI16[], + long interfero_bufSize, long *ErrorCode); + +void __cdecl LoadMeasurementInfo(SDK_Reference *SDKSessionID, char PhaseFile[], + ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl LoadMeasurement(SDK_Reference *SDKSessionID, char PhaseFile[], + ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, + float Intensity[], long Intensity_bufSize, long *ErrorCode); + +void __cdecl SaveLastMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], + unsigned short int MeasurementList[], long measurementList_bufSize, + char MeasurementFilesOut[], long filesOut_bufSize, long *ErrorCode); + +void __cdecl SaveMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], + ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, + float Intensity[], long Intensity_bufSize, char PhaseFileOut[], + long phaseFileOut_bufSize, char IntensityFileOut[], + long intensityFileOut_bufSize, long *ErrorCode); + + + +long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module); + +#pragma pack(pop) +''') + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) \ No newline at end of file diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index b97432aa..6abf8e8c 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 # -# Copyright 2017 Julio Mateos (julio.mateos-langerak@igh.cnrs.fr) +# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,374 +15,799 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Phasics SID4 SDK python interface +"""SID4_SDK wavefront sensor device. -This module provides a python interface to Phasics SDK to control -SID4 waveform sensors. -""" +This module provides a wrapper for SID4's SDK interface that allows +a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. -import ctypes -from ctypes import POINTER, Structure, c_int, c_uint8, c_ushort, c_long, c_ulong, c_float, c_char, c_ubyte, c_uint, c_double, c_void_p - -# Type definitions -LVBoolean = c_uint8 # 8-bit integer. 0 if False, 1 if True -SDK_Reference = c_int # 32-bit integer used to define a unique reference ID -SDK_WC = ctypes.c_wchar -SDK_CHAR = c_char -SDK_INT = c_int -SDK_LONG = c_long -SDK_DOUBLE = c_double -SDK_ULONG = c_ulong -SDK_FLOAT = c_float -SDK_UCHAR = c_ubyte -SDK_USHORT = c_ushort - -class SDK_Reference(Structure): - _fields_ = [('SDK_Reference', SDK_INT)] - -class ArraySize(Structure): - """This structure contains information about 2D array size: - it gives the size of each dimension. - Note: this structure is used to initialize the size of all the array - containing the phase map information. - So this structure is often used in the SDK SID4 and particularly in the - Interferogram Analysis Function. - - :parameter nRow: Long integer that specifies the size of the first dimension, - in other words it gives the rows number - :parameter nCol: Long integer that specifies the size of the second dimension, - in other words it gives the columns number - """ - _fields_ = [('nRow', SDK_LONG), - ('nCol', SDK_LONG)] - -class AnalysisInfo(Structure): - """This structure type contains information on the user profile - currently in memory, such as its location on the hard drive, - the current reference file, grating position (in mm) - and the wavelength (in nm) used as the unit for the phase values. - The PhaseSize variable is very important since it allows sizing - the phase and intensity maps used as inputs for the interferogram - analysis functions. - - This structure is filled by the GetUserProfile function - - :parameter GratingPositionMm: Double precision float giving - the grating position in millimeters. - This distance corresponds to the one between the diffraction grating and the CCD sensor of the SID4 - :parameter wavelengthNm: Double precision float that gives the wevelength currently used in nanometer - :parameter RemoveBackgroundImage: Boolean specifying if the background image is removed or not: - - false (value 0): the image background is not removed - - true (value 1): the image background is removed - :parameter PhaseSize_width: Long integer specifying the first dimension size of the phase map. - In other words it gives the number of lines - :parameter PhaseSize_Height: Long integer specifying the second dimension size of the phase map. - In other words it gives the number of columns - """ - _fields_ = [('GratingPositionMm', SDK_DOUBLE), - ('wavelengthNm', SDK_DOUBLE), - ('RemoveBackgroundImage', LVBoolean), - ('PhaseSize_width', SDK_LONG), - ('PhaseSize_Height', SDK_LONG)] - -class CameraInfo(Structure): - """This structure type contains information on the camera settings - associated with the user profile currently in memory. - - The camera parameters like the frame rate are indicates - in order to remain the SDK compatible with older Teli cameras. - If your camera are not a Teli one the frame rate, trigger mode, - gain, exposure time parameter should not be taken into account. - On the other side the parameters pixel size and number of camera - recorded are always correct. - - Regardless of the type of camera you use, the parameters - of this one can be recovered through function Camera_GetAttribute. - - This structure is filled by the GetUserProfile function - :parameter FrameRate: The following values are to be taken into account only if your camera is a TELI: - Camera frame rate: 0 = "3.75hz", 1 = "7.5hz", 2 = "15hz", 3 = "30hz", 4 = "60hz" - :parameter TriggerMode: The following values are to be taken into account only if your camera is a TELI: - Camera trigger mode: 0 = continous mode; 1 = trigger mode0; 2 = trigger mode1. - :parameter Gain: The following values are to be taken into account only if your camera is a TELI: - Camera gain. The range is [40;208] - :parameter ExposureTime: The following values are to be taken into account only if your camera is a TELI: - Camera exposure time: 0 = "1/60s", 1 = "1/200s", 2 = "1/500s", 3 = "1/1000s", 4 = "1/2000s", 5 = "1/4000s", - 6 = "1/8000s", 7 = "1/20000s". - :parameter PixelSizeM: Size of the camera pixel (µm) - :parameter NumberOfCameraRecorded: Number of recorded camera in the SID4 SDK software - """ - _fields_ = [('FrameRate', SDK_LONG), - ('TriggerMode', SDK_ULONG), - ('Gain', SDK_LONG), - ('ExposureTime', SDK_ULONG), - ('PixelSizeM', SDK_FLOAT), - ('NumberOfCameraRecorded', SDK_UCHAR)] - -class ZernikeInformation(Structure): - """This structure type contains information on the projection base - and the number of polynomials. - - The base can be either Zernike or Legendre. - The number of polynomials depends on the value of the highest polynomial order - - :parameter Base: Unsigned short int that specifies the polynomials base (Zernike=0 and Legendre=1) - :parameter Polynomials: Long integer that specifies the number of polynomials - """ - _fields_ = [('Base', SDK_USHORT), - ('Polynomials', SDK_LONG)] - -class ZernikeParam(Structure): - """This structure type contains information useful for Zernike calculation. - It defines the size of the intensity map and the size of the mask - the program will use to compute the Zernike coefficients. - It also defines the projection base (Zernike or Legendre). - - :parameter ImageRowSize: Unsigned long integer that specifies the number of rows of the intensity map - :parameter ImageColSize: Unsigned long integer that specifies the number of columns of the intensity map - :parameter MaskRowSize: Unsigned long integer that specifies the number of rows of the mask map - :parameter MaskColSize: Unsigned long integer that specifies the number of columns of the mask map - :parameter Base: Unsigned short int that specifies the polynomials base (Zernike=0 and Legendre=1) +The interface with the SID4 SDK has been implemented using cffi API 'in line' +""" +from microscope import devices +from microscope.devices import WavefrontSensorDevice, keep_acquiring +from cffi import FFI +import Pyro4 +import numpy as np +import time +# Python 2.7 to 3 +try: + import queue +except: + import Queue as queue + +# Trigger mode to type. +TRIGGER_MODES = { + 0: devices.TRIGGER_SOFT, # 0:'continuous mode' TODO: check all this + 1: devices.TRIGGER_SOFT, # 1:'mode0' + 2: devices.TRIGGER_BEFORE # 2:'mode1' +} +FRAME_RATES = { + 0: '3.75hz', + 1: '7.5hz', + 2: '15hz', + 3: '30hz', + 4: '60hz' +} +EXPOSURE_TIMES = { # Asa string and in msec + 0: ('1/60s', 16.6667), + 1: ('1/200s', 5.0), + 2: ('1/500s', 2.0), + 3: ('1/1000s', 1.0), + 4: ('1/2000s', 0.5), + 5: ('1/4000s', 0.25), + 6: ('1/8000s', 0.125), + 7: ('1/20000s', 0.05) +} +REFERENCE_SOURCES = { + 0: 'SDK_WORKSHOP', # Workshop reference + 1: 'SDK_CAMERA', # Reference from a grabbed camera image + 2: 'SDK_FILE' # Reference from an interferogram file, which path is given by ReferencePath +} +ZERNIKE_BASES = {0: 'Zernike', 1: 'Legendre'} +CAMERA_ATTRIBUTES = { + 'Exposure': (0, 0, 7), + 'Gain': (1, 40, 210), + 'GainOffset': (2, -1, -1), + 'Trigger': (3, 0, 2), + 'FrameRate': (4, 1, 5), + 'ImageWidth': (5, -1, -1), + 'ImageHeight': (6, -1, -1), + 'TimeOut': (7, 1, 10000), + 'AE_On/Off': (8, -1, -1) +} + +INVALIDATES_BUFFERS = ['_simple_pre_amp_gain_control', '_pre_amp_gain_control', + '_aoi_binning', '_aoi_left', '_aoi_top', + '_aoi_width', '_aoi_height', ] + +# We setup some necessary configuration parameters +# TODO: Move this into the config file + +# This is the default profile path of the camera +WFS_PROFILE_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' + +# We import the headers definitions used by cffi from a file in order to avoid copyright issues +HEADER_DEFINITIONS = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" +SID4_SDK_DLL_PATH = "SID4_SDK.dll" +ZERNIKE_SDK_DLL_PATH = "Zernike_SDK.dll" + + +@Pyro4.expose +# @Pyro4.behavior('single') +class SID4Device(WavefrontSensorDevice): """ - _fields_ = [('ImageRowSize', SDK_ULONG), - ('ImageColSize', SDK_ULONG), - ('MaskRowSize', SDK_ULONG), - ('MaskColSize', SDK_ULONG), - ('Base', SDK_USHORT)] - -class TiltInfo(Structure): - """This structure type contains Tilt information (X and Y tilts) - removed from the output phase - - :parameter XTilt: X tilt removed from output phase given in milliradian - :parameter YTilt: Y tilt removed from output phase given in milliradian + This class represents the SID4 wavefront sensors + Important note: The header defs is a text file containing the headers from + the SID4_SDK.h file with some modifications. Namely all #include and #ifdef have been + removed. Also, the typedef of LVBoolean has been changed for 'unsigned char' + + :param header_defs: Absolute path to the header definitions from the SDK. + :param dll_path: name of, or absolute path to the SID4_SDK.dll file + :param wfs_profile_path: Absolute path to the profile file that has to be loaded at startup. + Must be a byte encoded string. """ - _fields_ = [('XTilt', SDK_FLOAT), - ('YTilt', SDK_FLOAT)] - -# Import the dll's - -_stdcall_libraries = {} -_stdcall_libraries['SID4_SDK'] = ctypes.WinDLL('SID4_SDK') - -# Get error codes - -NO_ERROR = 0 -# TODO: Get error codes from SID4_SDK_Constants.h through a subclass of Exception - -class DeviceError(Exception): - pass - -### Functions ### -STRING = POINTER(SDK_WC) - -# classes so that we do some magic and automatically add byrefs etc ... can classify outputs -class _meta(object): - pass - -class OUTPUT(_meta): - def __init__(self, val): - self.type = val - self.val = POINTER(val) - - def getVar(self, bufLen=0): - v = self.type() - return v, ctypes.byref(v) - + def __init__(self, + header_definitions=HEADER_DEFINITIONS, + sid4_sdk_dll_path=SID4_SDK_DLL_PATH, + zernike_sdk_dll_path=ZERNIKE_SDK_DLL_PATH, + wfs_profile_path=WFS_PROFILE_PATH, + camera_attributes=CAMERA_ATTRIBUTES, + **kwargs): + self.header_definitions = header_definitions + try: + with open(self.header_definitions, 'r') as self.header_definitions: + self.cdef_from_file = self.header_definitions.read() + except FileNotFoundError: + print('Unable to find "%s" header file.' % self.header_definitions) + exit(1) + except IOError: + print('Unable to open "%s"' % self.header_definitions) + exit(2) + finally: + if self.cdef_from_file == '' or None: + print('File "%s" is empty' % self.header_definitions) + exit(3) + + # Create here the interface to the SDK + self.ffi = FFI() + self.ffi.cdef(self.cdef_from_file, override=True) + self.SID4_SDK = self.ffi.dlopen(sid4_sdk_dll_path) + self.zernike_SDK = self.ffi.dlopen(zernike_sdk_dll_path) + + # Allocate all necessary instances to control the SID4 + self.buffer_size = 1024 # buffer size to be used to initialize some variables + + # Create main instances + self.session_id = self.ffi.new('SDK_Reference *') + self.error_code = self.ffi.new('long *', 0) + + # Create metadata instances + self.user_profile_name = self.ffi.new("char[]", self.buffer_size) + self.user_profile_name_bs = self.ffi.cast("long", self.buffer_size) + + self.user_profile_file = self.ffi.new("char[]", self.buffer_size) + self.user_profile_file_bs = self.ffi.cast("long", self.buffer_size) + self.user_profile_file = wfs_profile_path + + self.user_profile_description = self.ffi.new("char[]", self.buffer_size) + self.user_profile_description_bs = self.ffi.cast("long", self.buffer_size) + + self.user_profile_last_reference = self.ffi.new("char[]", self.buffer_size) + self.user_profile_last_reference_bs = self.ffi.cast("long", self.buffer_size) + + self.user_profile_directory = self.ffi.new("char[]", self.buffer_size) + self.user_profile_directory_bs = self.ffi.cast("long", self.buffer_size) + + self.sdk_version = self.ffi.new("char[]", self.buffer_size) + self.sdk_version_bs = self.ffi.cast("long", self.buffer_size) + + self.analysis_information = self.ffi.new("AnalysisInfo *") + self.camera_information = self.ffi.new("CameraInfo *") + + self.reference_source = self.ffi.cast("unsigned short int", 0) + self.reference_path = self.ffi.new("char[]", self.buffer_size) + self.reference_changed = self.ffi.cast("unsigned char", 0) + + self.camera_sn = self.ffi.new("char[]", self.buffer_size) + self.camera_sn_bs = self.ffi.cast("long", self.buffer_size) + + self.camera_array_size = self.ffi.new('ArraySize *') + self.analysis_array_size = self.ffi.new('ArraySize *') + + # Create zernike-related parameters + self.zernike_information = self.ffi.new("ZernikeInformation *") + self.zernike_parameters = self.ffi.new("ZernikeParam *") + + self.zernike_version = self.ffi.new("char[]", self.buffer_size) + self.zernike_version_bs = self.ffi.cast("long", self.buffer_size) + + self.polynomials_list = self.ffi.new("char[]", self.buffer_size) + self.polynomials_list_bs = self.ffi.cast("long", self.buffer_size) + + # Call super __init__. + super(SID4Device, self).__init__(**kwargs) + + # Create camera attributes. TODO: this should be created programmatically + self.camera_attributes = camera_attributes + + # Add profile settings + self.add_setting('user_profile_name', 'str', + lambda: self.ffi.string(self.user_profile_name), + None, + self.buffer_size, + readonly=True) + self.add_setting('user_profile_file', 'str', + lambda: self.ffi.string(self.user_profile_file), # TODO: this lambda will probably not work + self._set_user_profile_file, + self.buffer_size) + self.add_setting('user_profile_description', 'str', + lambda: self.ffi.string(self.user_profile_description), + None, + self.buffer_size, + readonly=True) + self.add_setting('user_profile_last_reference', 'str', + lambda: self.ffi.string(self.user_profile_last_reference), + None, + self.buffer_size, + readonly=True) + self.add_setting('user_profile_last_directory', 'str', + lambda: self.ffi.string(self.user_profile_directory), + None, + self.buffer_size, + readonly=True) + self.add_setting('sdk_version', 'str', + lambda: self.ffi.string(self.sdk_version), + None, + self.buffer_size, + readonly=True) + + # Add camera settings + self.add_setting('frame_rate', 'enum', + lambda: FRAME_RATES[self.camera_information.FrameRate], + self._set_frame_rate, + lambda: FRAME_RATES.keys()) + self.add_setting('trigger_mode', 'enum', + lambda: TRIGGER_MODES[self.camera_information.TriggerMode], + self._set_trigger_mode, + lambda: TRIGGER_MODES.keys()) + self.add_setting('gain', 'int', + lambda: self.camera_information.Gain, + self._set_gain, + lambda: (40, 210)) + self.add_setting('exposure_time', 'enum', + lambda: self.camera_information.ExposureTime, + self._set_exposure_time, + lambda: EXPOSURE_TIMES.keys()) + self.add_setting('exposure_time_ms', 'float', + lambda: EXPOSURE_TIMES[self.camera_information.ExposureTime][1], + None, # The SID4 does not support an arbitrary exposure time. + lambda: (0.05, 17.0), + readonly=True) + self.add_setting('camera_pixel_size_m', 'float', + lambda: self.camera_information.PixelSizeM, + None, + lambda: (0.0, 0.1), + readonly=True) + self.add_setting('camera_number_rows', 'int', + lambda: self.camera_array_size.nRow, + None, + lambda: (0, 480), + readonly=True) + self.add_setting('camera_number_cols', 'int', + lambda: self.camera_array_size.nCol, + None, + lambda: (0, 640), + readonly=True) + self.add_setting('number_of_camera_recorded', 'int', + lambda: self.camera_information.NumberOfCameraRecorded, + None, + lambda: (0, 255), + readonly=True) + + # Add analysis settings + self.add_setting('reference_source', 'enum', + lambda: int(self.reference_source), + self._set_reference_source, + lambda: REFERENCE_SOURCES.keys()) + self.add_setting('reference_path', 'str', + lambda: int(self.reference_path), + self._set_reference_path, + self.buffer_size) + self.add_setting('grating_position_mm', 'float', + lambda: self.analysis_information.GratingPositionMm, + None, + (0.0, 300.0), # TODO: Verify these values + readonly=True) + self.add_setting('wavelength_nm', 'float', + self._get_wavelength_nm, + self._set_wavelength_nm, + (400.0, 1100.0)) + self.add_setting('remove_background_image', 'bool', + lambda: self.analysis_information.RemoveBackgroundImage, + self._set_remove_background_image, + None) + self.add_setting('phase_size_width', 'int', + lambda: self.analysis_information.PhaseSize_width, + None, + (0, 160), + readonly=True) + self.add_setting('phase_size_height', 'int', + lambda: self.analysis_information.PhaseSize_Height, + None, + (0, 120), + readonly=True) + self.add_setting('zernike_base', 'enum', + self._get_zernike_base, + self._set_zernike_base, + lambda: ZERNIKE_BASES.values()) + self.add_setting('zernike_polynomials', 'int', + self._get_zernike_polynomials, + self._set_zernike_polynomials, + (0, 254)) + self.add_setting('image_row_size', 'int', + lambda: self.zernike_parameters.ImageRowSize, + None, + (0, 480), + readonly=True) + self.add_setting('image_col_size', 'int', + lambda: self.zernike_parameters.ImageColSize, + None, + (0, 640), + readonly=True) + self.add_setting('mask_row_size', 'int', + lambda: self.zernike_parameters.MaskRowSize, + None, + (0, 480), + readonly=True) + self.add_setting('mask_col_size', 'int', + lambda: self.zernike_parameters.MaskColSize, + None, + (0, 640), + readonly=True) + self.add_setting('zernike_version', 'str', + lambda: self.zernike_version, + None, + self.buffer_size, + readonly=True) + + # Software buffers and parameters for data conversion. + self.num_buffers = 32 + self.add_setting('num_buffers', 'int', + lambda: self.num_buffers, + lambda val: self.set_num_buffers(val), + (1, 100)) + self.buffers = queue.Queue() + self.filled_buffers = queue.Queue() + self._buffer_size = None + self._buffers_valid = False + self._exposure_callback = None + + # @property + # def _acquiring(self): + # return self._camera_acquiring.get_value() + # + # @keep_acquiring + # def _enable_callback(self, use=False): + # pass # TODO: Verify if we need this + # + # @_acquiring.setter + # def _acquiring(self, value): + # pass # TODO: Verify if we need this + + def set_num_buffers(self, num): + self.num_buffers = num + self._buffers_valid = False + + def _purge_buffers(self): + """Purge buffers on both camera and PC.""" + self._logger.debug("Purging buffers.") + self._buffers_valid = False + if self._acquiring: + raise Exception('Can not modify buffers while camera acquiring.') + # TODO: implement the flush + while True: + try: + self.buffers.get(block=False) + except queue.Empty: + break + + def _create_buffers(self, num=None): + """Create buffers and store values needed to remove padding later.""" + if self._buffers_valid: + return + if num is None: + num = self.num_buffers + self._purge_buffers() + self._logger.debug("Creating %d buffers." % num) + intensity_map_size = self._intensity_map_size.get_value() + phase_map_size = self._phase_map_size.get_value() + zernike_indexes_size = self._zernike_indexes_size.get_value() + for i in range(num): + intensity_buf = np.require(np.empty(intensity_map_size), + dtype='uint32', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + phase_buf = np.require(np.empty(phase_map_size), + dtype='float32', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + # Create data instances in either way + self.tilt_information = self.ffi.new('TiltInfo *') + self.projection_coefficients_in = self.ffi.new("double[]", zernike_indexes_size) + + tilts_buf = np.require(np.empty(2), + dtype='float32', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + rms_buf = np.require(np.empty(1), + dtype='float32', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + ptv_buf = np.require(np.empty(1), + dtype='int32', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + zernike_buf = np.require(np.empty(zernike_indexes_size), + dtype='float64', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'OWNDATA']) + + self.buffers.put([intensity_buf, + phase_buf, + tilts_buf, + rms_buf, + ptv_buf, + zernike_buf]) + self._buffers_valid = True + + def invalidate_buffers(self, func): + """Wrap functions that invalidate buffers so buffers are recreated.""" + outerself = self + def wrapper(self, *args, **kwargs): + func(self, *args, **kwargs) + outerself._buffers_valid = False + return wrapper + + def _fetch_data(self, timeout=5, debug=False): + """Fetch data and recycle buffers.""" + try: + raw = self.filled_buffers.get(timeout=timeout) + except: + raise Exception('There is no data in the buffer') + data = [np.copy(x) for x in raw] + + # Requeue the buffer. TODO: we should check if buffer size has not been changed elsewhere. + self.buffers.put(raw) + + return data + + def abort(self): + """Abort acquisition.""" + self._logger.debug('Aborting acquisition.') + if self._acquiring: + self._acquisition_stop() + + def initialize(self): + """Initialize the SID4 + + Opens the connection to the SDK, initializes the SID4 and populates the settings + from the input profile""" + self._logger.debug('Opening SDK...') + try: + self._set_user_profile_file(self.user_profile_file) + except: + raise Exception('SDK could not open.') + + # get the camera attributes and populate them + # self._get_camera_attribute_list() + + # Default setup + # self.trigger_mode.set_string('Software')z + # self._cycle_mode.set_string('Continuous') + + # def callback(*args): + # data = self._fetch_data(timeout=500) + # timestamp = time.time() + # if data is not None: + # self._dispatch_buffer.put((data, timestamp)) + # return 0 + # else: + # return -1 + # + # self._exposure_callback = SDK3.CALLBACKTYPE(callback) + + # def _get_camera_attribute_list(self): + # """This method updates the camera attributes stored in the camera_attributes dict + # + # To call at camera initialization""" + # self.nr_of_attributes = self.ffi.new('long *') + # print(self.nr_of_attributes[0]) + # # try: + # # self.SID4_SDK.Camera_GetNumberOfAttribute(self.session_id, self.nr_of_attributes, self.error_code) + # # except: + # # raise Exception('Could not get nr_of_attributes.') + # print(self.error_code[0]) + # print(self.nr_of_attributes[0]) + # + # self.attribute_id = self.ffi.new('unsigned short int[]', self.nr_of_attributes[0]) + # self.attribute_id_bs = self.ffi.cast('long', self.nr_of_attributes[0]) + # self.attributes = self.ffi.new('char[]', self.buffer_size) + # self.attributes_bs = self.ffi.cast('long', self.buffer_size) + # self.attribute_min = self.ffi.new('long[]', self.nr_of_attributes[0]) + # self.attribute_min_bs = self.ffi.cast('long', self.nr_of_attributes[0] * 4) + # self.attribute_max = self.ffi.new('long[]', self.nr_of_attributes[0]) + # self.attribute_max_bs = self.ffi.cast('long', self.nr_of_attributes[0] * 4) + # # self.attribute_id = self.ffi.new('unsigned short int[]', 9) + # # self.attribute_id_bs = self.ffi.cast('long', 36) + # # self.attributes = self.ffi.new('char[]', 1024) + # # self.attributes_bs = self.ffi.cast('long', 1024) + # # self.attribute_min = self.ffi.new('long[]', 9) + # # self.attribute_min_bs = self.ffi.cast('long', 36) + # # self.attribute_max = self.ffi.new('long[]', 9) + # # self.attribute_max_bs = self.ffi.cast('long', 36) + # + # self.SID4_SDK.Camera_GetAttributeList(self.session_id, + # self.attribute_id, + # self.attribute_id_bs, + # self.attributes, + # self.attributes_bs, + # self.attribute_min, + # self.attribute_min_bs, + # self.attribute_max, + # self.attribute_max_bs, + # self.error_code) + # + # print(self.error_code[0]) + # + # self.attributes_list = self.ffi.string(self.attributes) # [:-2].split(sep = b'\t') + # + # for i in range(self.nr_of_attributes[0]): + # self.camera_attributes[self.attributes_list[i]] = (self.attribute_id[i], + # self.attribute_min[i], + # self.attribute_max[i]) + + def _on_enable(self): + self._logger.debug('Enabling SID4.') + if self._acquiring: + self._acquisition_stop() + self._create_buffers() + + self._logger.debug('Initializing SID4...') + try: + self.SID4_SDK.CameraInit(self.session_id, self.error_code) + except: + raise Exception('SID4 could not Init. Error code: ', self.error_code[0]) + + def _acquisition_start(self): + try: + self.SID4_SDK.CameraStart(self.session_id, self.error_code) + if not self.error_code[0]: + self._acquiring = True + except: + raise Exception('Unable to enable SID4. Error code: ', str(self.error_code[0])) + + def _acquisition_stop(self): + try: + self.SID4_SDK.CameraStop(self.session_id, self.error_code) + if not self.error_code[0]: + self._acquiring = False + except: + raise Exception('Unable to stop acquisiiton. Error code: ', str(self.error_code[0])) + + def _on_disable(self): + self.abort() + self._buffers_valid = False + try: + self.SID4_SDK.CameraClose(self.session_id, self.error_code) + except: + raise Exception('Unable to close camera. Error code: ', str(self.error_code[0])) + + def _on_shutdown(self): + self.abort() + self.disable() + try: + self.SID4_SDK.CloseSID4(self.session_id, self.error_code) + except: + raise Exception('Unable to close SDK. Error code: ', str(self.error_code[0])) + + # phase = ffi.new('float[]', 4096) + # phase_bs = ffi.cast('long', 16384) + # intensity = ffi.new('float[]', 4096) + # intensity_bs = ffi.cast('long', 16384) + # + # image = ffi.new("short int[307200]") + # image_bs = ffi.cast("long", 307200) + # + # + # + # ffi.string(self.user_profile_name) + # int(user_profile_name_bs) + + # print(analysis_information.GratingPositionMm) + # print(analysis_information.wavelengthNm) + # print(analysis_information.RemoveBackgroundImage) + # print(analysis_information.PhaseSize_width) + # print(analysis_information.PhaseSize_Height) + # + # print(camera_information.FrameRate) + # print(camera_information.TriggerMode) + # print(camera_information.Gain) + # print(camera_information.ExposureTime) + # print(camera_information.PixelSizeM) + # print(camera_information.NumberOfCameraRecorded) + # + # + # print('Starting Live mode...') + # SDK.StartLiveMode(self.session_id, self.error_code) + # print(self.error_code[0]) + # + # print('Grabbing image...') + # SDK.GrabImage(self.session_id, image, image_bs, camera_array_size, self.error_code) + # print(self.error_code[0]) + # + # print('Part of the image') + # print([x for x in image[0:20]]) + # + # print('Grabbing Live mode...') + # SDK.GrabLiveMode(self.session_id, phase, phase_bs, intensity, intensity_bs, self.tilt_information, self.analysis_array_size, self.error_code) + # print(self.error_code[0]) + ## + ##print('Part of the phase') + ##print([x for x in phase[0:20]]) + ## + ##print('Part of the intensity') + ##print([x for x in intensity[0:20]]) + ## + ##print('Stopping Live mode...') + ##SDK.StopLiveMode(session_id, error_code) + ##print(error_code[0]) + ## + ##print('Closing camera...') + ##SDK.CameraClose(session_id, error_code) + ##print(error_code[0]) + ## + ##print('Closing SDK...') + ##SDK.CloseSID4(session_id, error_code) + ##print(error_code[0]) + ## + ### keep phase alive + ##a = phase + + @keep_acquiring + def _set_user_profile_file(self, path=None): + """Sets the user profile file but also reloads the SDK with OpenSID4 and + repopulates the settings with GetUserProfile""" + self.user_profile_file = path + try: + self.SID4_SDK.OpenSID4(self.user_profile_file, self.session_id, self.error_code) + self.SID4_SDK.GetUserProfile(self.session_id, + self.user_profile_name, + self.user_profile_name_bs, + self.user_profile_file, + self.user_profile_file_bs, + self.user_profile_description, + self.user_profile_description_bs, + self.user_profile_last_reference, + self.user_profile_last_reference_bs, + self.user_profile_directory, + self.user_profile_directory_bs, + self.sdk_version, + self.sdk_version_bs, + self.analysis_information, + self.camera_information, + self.camera_sn, + self.camera_sn_bs, + self.analysis_array_size, + self.error_code) + except: + raise Exception('SDK could not open. Error code: ', self.error_code[0]) + + print(self.session_id[0]) + print(self.ffi.string(self.user_profile_name)) + print(int(self.user_profile_name_bs)) + print(self.user_profile_file) + print(int(self.user_profile_file_bs)) + print(self.ffi.string(self.user_profile_description)) + print(int(self.user_profile_description_bs)) + print(self.ffi.string(self.user_profile_last_reference)) + print(int(self.user_profile_last_reference_bs)) + print(self.ffi.string(self.user_profile_directory)) + print(int(self.user_profile_directory_bs)) + print(self.ffi.string(self.sdk_version)) + print(int(self.sdk_version_bs)) + print(self.analysis_information.GratingPositionMm) + print(self.analysis_information.wavelengthNm) + print(self.analysis_information.RemoveBackgroundImage) + print(self.analysis_information.PhaseSize_width) + print(self.analysis_information.PhaseSize_Height) + print(self.camera_information.FrameRate) + print(self.camera_information.TriggerMode) + print(self.camera_information.Gain) + print(self.camera_information.ExposureTime) + print(self.camera_information.PixelSizeM) + print(self.camera_information.NumberOfCameraRecorded) + print(self.ffi.string(self.camera_sn)) + print(int(self.camera_sn_bs)) + print(self.analysis_array_size.nRow) + print(self.analysis_array_size.nCol) + print(self.error_code[0]) + + @keep_acquiring + def _set_camera_attribute(self, attribute, value): + attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) + new_value = self.ffi.cast('double', value) + try: + self.SID4_SDK.Camera_SetAttribute(self.session_id, + attribute_id, + new_value, + self.error_code) + except: + raise Exception('Could not change camera attribute: %s', attribute) + + def _modify_user_profile(self, save=False): + + try: + self.SID4_SDK.ModifyUserProfile(self.session_id, + self.analysis_information, + self.reference_source, + self.reference_path, + self.user_profile_description, + self.reference_changed, + self.error_code) + except: + Exception('Could not modify user profile') + + if save: + self._save_user_profile() + + def _save_user_profile(self): + try: + self.SID4_SDK.SaveCurrentUserProfile(self.session_id, self.error_code) + except: + Exception('Could not save user profile') + + def _set_frame_rate(self, rate): + self._set_camera_attribute('FrameRate', rate) + if self.error_code[0]: + self.camera_information.FrameRate = rate + + def _set_trigger_mode(self, mode): + self._set_camera_attribute('Trigger', mode) + if self.error_code[0]: + self.camera_information.TriggerMode = mode + + def _set_gain(self, gain): + self._set_camera_attribute('Gain', gain) + if self.error_code[0]: + self.camera_information.Gain = gain + + def _set_exposure_time(self, index): + self._set_camera_attribute('Exposure', index) + if self.error_code[0]: + self.camera_information.ExposureTime = index + + def _set_reference_source(self, source, save=False): + self.reference_source = source + self._modify_user_profile(save=save) + + def _set_reference_path(self, path, save=False): + self.reference_path = path + self._modify_user_profile(save=save) + + def _get_wavelength_nm(self): + return self.analysis_information.wavelengthNm + + def _set_wavelength_nm(self, wavelength, save=False): + self.analysis_information.wavelengthNm = wavelength + self._modify_user_profile(save=save) + + def _set_remove_background_image(self, remove, save=False): + if remove: + self.analysis_information.RemoveBackgroundImage = 1 + else: + self.analysis_information.RemoveBackgroundImage = 0 -class _OUTSTRING(OUTPUT): - def __init__(self): - self.val = STRING + self._modify_user_profile(save=save) - def getVar(self, bufLen=0): - v = ctypes.create_unicode_buffer(bufLen) - return v, v + def _set_phase_size_width(self, width, save=False): + self.analysis_information.PhaseSize_width = width + self._modify_user_profile(save=save) -OUTSTRING = _OUTSTRING() + def _set_phase_size_height(self, height, save=False): + self.analysis_information.PhaseSize_Height = height + self._modify_user_profile(save=save) -class _OUTSTRLEN(_meta): - def __init__(self): - self.val = c_int + def _get_intensity_size_width(self): + pass -OUTSTRLEN = _OUTSTRLEN() + def _set_intensity_size_width(self): + pass -def stripMeta(val): - if isinstance(val, _meta): - return val.val - else: - return val + def _get_intensity_size_height(self): + pass -class dllFunction(object): - def __init__(self, name, args=[], argnames=[], lib='SID4_SDK'): - self.f = getattr(_stdcall_libraries[lib], name) - self.f.restype = c_int - self.f.argtypes = [stripMeta(a) for a in args] + def _set_intensity_size_height(self): + pass - self.fargs = args - self.fargnames = argnames - self.name = name + def _get_zernike_base(self): + pass - self.inp = [not isinstance(a, OUTPUT) for a in args] - self.in_args = [a for a in args if not isinstance(a, OUTPUT)] - self.out_args = [a for a in args if isinstance(a, OUTPUT)] + def _set_zernike_base(self): + pass - self.buf_size_arg_pos = -1 - for i in range(len(self.in_args)): - if isinstance(self.in_args[i], _OUTSTRLEN): - self.buf_size_arg_pos = i + def _get_zernike_polynomials(self): + pass - ds = name + '\n\nArguments:\n===========\n' - for i in range(len(args)): - an = '' - if i < len(argnames): - an = argnames[i] - ds += '\t%s\t%s\n' % (args[i], an) + def _set_zernike_polynomials(self): + pass - self.f.__doc__ = ds - def __call__(self, *args): - ars = [] - i = 0 - ret = [] +if __name__ == '__main__': - if self.buf_size_arg_pos >= 0: - bs = args[self.buf_size_arg_pos] - else: - bs = 255 - - for j in range(len(self.inp)): - if self.inp[j]: # an input - ars.append(args[i]) - i += 1 - else: # an output - r, ar = self.fargs[j].getVar(bs) - ars.append(ar) - ret.append(r) - # print r, r._type_ - - # print ars - res = self.f(*ars) - # print res - - if not res == NO_ERROR: - raise DeviceError(self.name, res) - - if len(ret) == 0: - return None - if len(ret) == 1: - return ret[0] - else: - return ret - -def dllFunc(name, args=[], argnames=[], lib='SID4_SDK'): - f = dllFunction(name, args, argnames, lib) - globals()[name] = f - -# Configuration Functions - -dllFunc('OpenSID4', - [SDK_CHAR, OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['UserProfileLocation[]', '*SessionID', '*ErrorCode']) -dllFunc('CloseSID4', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SessionID', '*ErrorCode']) -dllFunc('GetUserProfile', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, - SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, - SDK_CHAR, SDK_LONG, SDK_CHAR, SDK_LONG, OUTPUT(AnalysisInfo), - OUTPUT(CameraInfo), SDK_CHAR, SDK_LONG, OUTPUT(ArraySize), OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'UserProfile_Name[]', 'uspName_bufSize', 'UserProfile_File[]', 'uspFile_bufSize', - 'UserProfile_Description[]', 'uspDesc_bufSize', 'UsrP_LatestReference[]', 'uspLastRef_bufSize', - 'UserProfileDirectory[]', 'uspDir_bufSize', 'SDKVersion[]', 'version_bufSize', '*AnalysisInformation', - '*CameraInformation', 'SNPhasics[]', 'SNPhasics_bufSize', '*AnalysisArraySize', '*ErrorCode']) -dllFunc('ChangeReference', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_USHORT, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'ReferencePath[]', 'ReferenceSource', 'ArchivedPath[]', 'ArchivedPath_bufSize', '*ErrorCode']) -dllFunc('ChangeMask', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', - 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_Contours_coordinates[]', 'contoursCoord_bufSize', - '*ErrorCode']) -dllFunc('SetBackground', - [OUTPUT(SDK_Reference), SDK_USHORT, SDK_CHAR, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'Source', 'BackgroundFile[]', 'UpdatedBackgroundImageFile[]', 'updatedImageFile_bufSize', - '*ErrorCode']) -dllFunc('LoadMaskDescriptorInfo', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', - 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', - '*ErrorCode']) -dllFunc('LoadMaskDescriptor', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, OUTPUT(SDK_USHORT), SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', '*ROI_NbOfContours', - 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', - '*ErrorCode']) -dllFunc('ModifyUserProfile', - [OUTPUT(SDK_Reference), OUTPUT(AnalysisInfo), SDK_USHORT, SDK_CHAR, SDK_CHAR, OUTPUT(LVBoolean), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*AnalysisInformation', 'ReferenceSource', 'ReferencePath[]', 'UserProfile_Decription[]', - '*ReferenceChanged', '*ErrorCode']) -dllFunc('NewUserProfile', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_CHAR, SDK_CHAR, SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'CameraSNPhasics[]', 'ProfileName[]', 'UserProfileDirectory[]', 'ProfilePathFileOut[]', - 'pathFileOut_bufSize', '*ErrorCode']) -dllFunc('SaveMaskDescriptor', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, SDK_LONG, SDK_USHORT, SDK_ULONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'MaskFile[]', 'ROI_GlobalRectangle[]', 'globalRect_bufSize', 'ROI_NbOfContours', - 'ROI_Contours_info[]', 'contoursInfo_bufSize', 'ROI_contours_coordinates[]', 'contoursCoord_bufSize', - '*ErrorCode']) -dllFunc('SaveCurrentUserProfile', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) - -# Camera Control Functions - -dllFunc('CameraList', - [OUTPUT(SDK_Reference), SDK_CHAR, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'CameraList_SNPhasics[]', 'camList_bufSize', '*ErrorCode']) -# Obsolete -# dllFunc('CameraSetup', -# [OUTPUT(SDK_Reference), SDK_USHORT, SDK_USHORT, OUTPUT(SDK_LONG)], -# ['*SDKSessionID', 'CameraParameter', 'Value', '*ErrorCode']) -dllFunc('CameraInit', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('CameraStart', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('CameraStop', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('CameraClose', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('StartLiveMode', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('StopLiveMode', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*ErrorCode']) -dllFunc('Camera_GetAttribute', - [OUTPUT(SDK_Reference), SDK_USHORT, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'AttributeID', '*AttributeValueOut', '*ErrorCode']) -dllFunc('Camera_GetAttributeList', - [OUTPUT(SDK_Reference), SDK_USHORT, SDK_LONG, SDK_CHAR, SDK_LONG, - SDK_LONG, SDK_LONG, SDK_LONG, SDK_LONG, OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'AttributeID[]', 'attribID_bufSize', 'AttributeName_SeparatedByTab[]', 'attribName_bufSize', - 'AttributeGmin[]', 'attribGmin_bufSize', 'AttributeGmax[]', 'attribGmax_bufSize', '*ErrorCode']) -dllFunc('Camera_SetAttribute', - [OUTPUT(SDK_Reference), SDK_USHORT, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'AttributeID', '*AttributeValue', '*ErrorCode']) -dllFunc('Camera_GetNumberOfAttribute', - [OUTPUT(SDK_Reference), OUTPUT(SDK_LONG), OUTPUT(SDK_LONG)], - ['*SDKSessionID', '*NumberOfAttribute', '*ErrorCode']) -dllFunc('Camera_ConvertExposureMs', - [OUTPUT(SDK_Reference), SDK_DOUBLE, OUTPUT(SDK_DOUBLE), OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'ExposureRawValueIn', '*ExposureValueMsOut', '*ErrorCode']) - -# Inteferogram Analysis functions - -dllFunc('ArrayAnalysis', - [OUTPUT(SDK_Reference), SDK_INT, SDK_LONG, SDK_FLOAT, SDK_LONG, - SDK_FLOAT, SDK_LONG, OUTPUT(TiltInfo), OUTPUT(ArraySize), OUTPUT(ArraySize), OUTPUT(SDK_LONG)], - ['*SDKSessionID', 'InterferogramInArrayI16[]', 'Interfero_bufSize', 'Intensity[]', 'Intensity_bufSize', - 'Phase[]', 'Phase_bufSize', '*TiltInformation', '*AnalysisArraySize', '*ImageCameraSize', '*ErrorCode']) \ No newline at end of file + wfs = SID4Device() + wfs.initialize() diff --git a/microscope/wavefront_sensors/SID4_SDK_cffi.py b/microscope/wavefront_sensors/SID4_SDK_cffi.py new file mode 100644 index 00000000..dd4f0e51 --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_cffi.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# -*- coding: utf-8 +# +# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""SID4_SDK wavefront sensor device. + +This class provides a wrapper for SID4's SDK interface that allows +a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. +""" + +from cffi import FFI + + +# We import the headers from a file in order to avoid copyright issues +wfs_profile_path = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' +header_defs = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" +header_path = "C:\\Program Files (x86)\\SID4_SDK\\DLL SDK\\Headers\\SID4_SDK.h" +dll_path = "SID4_SDK.dll" +cdef_from_file = '' + +try: + with open(header_defs, 'r') as header_defs: + cdef_from_file = header_defs.read() +except FileNotFoundError: + print('Unable to find "%s" header file.' % header_defs) + exit(1) +except IOError: + print('Unable to open "%s"' % header_defs) + exit(2) +finally: + if cdef_from_file == '' or None: + print('File "%s" is empty' % header_defs) + exit(3) + +ffi = FFI() + +ffi.cdef(cdef_from_file) + +SDK = ffi.dlopen(dll_path) + +buffer_size = 1024 + +nrow = 0 +ncol = 0 + +user_profile_name = ffi.new("char[]", buffer_size) # initialize first with a certain buffer size +user_profile_name_bs = ffi.cast("long", buffer_size) + +user_profile_file = ffi.new("char[]", buffer_size) +user_profile_file_bs = ffi.cast("long", buffer_size) +user_profile_file = wfs_profile_path + +user_profile_description = ffi.new("char[]", buffer_size) +user_profile_description_bs = ffi.cast("long", buffer_size) + +user_profile_last_reference = ffi.new("char[]", buffer_size) +user_profile_last_reference_bs = ffi.cast("long", buffer_size) + +user_profile_last_directory = ffi.new("char[]", buffer_size) +user_profile_last_directory_bs = ffi.cast("long", buffer_size) + +sdk_version = ffi.new("char[]", buffer_size) +sdk_version_bs = ffi.cast("long", buffer_size) + +analysis_information = ffi.new("AnalysisInfo *") + +camera_information = ffi.new("CameraInfo *") + +camera_sn = ffi.new("char[]", buffer_size) +camera_sn_bs = ffi.cast("long", buffer_size) + +session_id = ffi.new('SDK_Reference *') +error_code = ffi.new('long *', 0) + +tilt_information = ffi.new('TiltInfo *') +analysis_array_size = ffi.new('ArraySize *', [64,64]) +camera_array_size = ffi.new('ArraySize *') + +print('Opening SDK...') +SDK.OpenSID4(user_profile_file, session_id, error_code) +print(error_code[0]) + +print('Initializing WFS...') +SDK.CameraInit(session_id, error_code) +print(error_code[0]) + +##print('Starting WFS...') +##SDK.CameraStart(session_id, error_code) +##print(error_code[0]) +## +##print('Stoping WFS...') +##SDK.CameraStop(session_id, error_code) +##print(error_code[0]) + +SDK.GetUserProfile(session_id, + user_profile_name, + user_profile_name_bs, + user_profile_file, + user_profile_file_bs, + user_profile_description, + user_profile_description_bs, + user_profile_last_reference, + user_profile_last_reference_bs, + user_profile_last_directory, + user_profile_last_directory_bs, + sdk_version, + sdk_version_bs, + analysis_information, + camera_information, + camera_sn, + camera_sn_bs, + camera_array_size, + error_code) + +ffi.string(user_profile_name) +int(user_profile_name_bs) + +print('Grating Position', analysis_information.GratingPositionMm) +print('wavelengthNm', analysis_information.wavelengthNm) +print('RemoveBackgroundImage', analysis_information.RemoveBackgroundImage) +print('PhaseSize_width', analysis_information.PhaseSize_width) +print('PhaseSize_Height', analysis_information.PhaseSize_Height) + +print('FrameRate', camera_information.FrameRate) +print('TriggerMode', camera_information.TriggerMode) +print('gain', camera_information.Gain) +print('Exp Time', camera_information.ExposureTime) +print('Pixel Size', camera_information.PixelSizeM) +print('NumberOfCameraRecorded', camera_information.NumberOfCameraRecorded) + +nr_of_attributes = ffi.new('long *') +SDK.Camera_GetNumberOfAttribute(session_id, nr_of_attributes, error_code) + +print('nr of attributes is: ') +print(nr_of_attributes[0]) + +attribute_id = ffi.new('unsigned short int[]', nr_of_attributes[0]) +attribute_id_bs = ffi.cast('long', nr_of_attributes[0]) +attribute_name = ffi.new('char[]', buffer_size) +attribute_name_bs = ffi.cast('long', buffer_size) +attribute_min = ffi.new('long[]', nr_of_attributes[0]) +attribute_min_bs = ffi.cast('long', nr_of_attributes[0] * 4) +attribute_max = ffi.new('long[]', nr_of_attributes[0]) +attribute_max_bs = ffi.cast('long', nr_of_attributes[0] * 4) + +print('Getting camera attributes') +SDK.Camera_GetAttributeList(session_id, + attribute_id, + attribute_id_bs, + attribute_name, + attribute_name_bs, + attribute_min, + attribute_min_bs, + attribute_max, + attribute_max_bs, + error_code) +print(error_code[0]) + +print('Attributes ids:') +print(ffi.unpack(attribute_id, nr_of_attributes[0])) +print('Names:') +print(ffi.string(attribute_name)) +print('Min values:') +print(ffi.unpack(attribute_min, nr_of_attributes[0])) +print('Max values:') +print(ffi.unpack(attribute_max, nr_of_attributes[0])) + + + +phase = ffi.new('float[]', 4096) +phase_bs = ffi.cast('long', 16384) +intensity = ffi.new('float[]', 4096) +intensity_bs = ffi.cast('long', 16384) + +image = ffi.new("short int[307200]") +image_bs = ffi.cast("long", 307200) + +print('Starting Live mode...') +SDK.StartLiveMode(session_id, error_code) +print(error_code[0]) + +print('Grabbing image...') +SDK.GrabImage(session_id, image, image_bs, camera_array_size, error_code) +print(error_code[0]) + +print('Part of the image') +print([x for x in image[0:20]]) + +print('Grabbing Live mode...') +SDK.GrabLiveMode(session_id, phase, phase_bs, intensity, intensity_bs, tilt_information, analysis_array_size, error_code) +print(error_code[0]) +## +##print('Part of the phase') +##print([x for x in phase[0:20]]) +## +##print('Part of the intensity') +##print([x for x in intensity[0:20]]) +## +##print('Stopping Live mode...') +##SDK.StopLiveMode(session_id, error_code) +##print(error_code[0]) +## +##print('Closing camera...') +##SDK.CameraClose(session_id, error_code) +##print(error_code[0]) +## +##print('Closing SDK...') +##SDK.CloseSID4(session_id, error_code) +##print(error_code[0]) +## +### keep phase alive +##a = phase From 2106f1461030ec221dd0f2b38d047cde89d6cc7e Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Tue, 31 Oct 2017 16:50:59 +0100 Subject: [PATCH 04/27] Analysis attributes retturned correctly from buffers --- microscope/devices.py | 12 +- microscope/wavefront_sensors/AndorSDK3.py | 297 ------ .../wavefront_sensors/AndorSDK3Camera.py | 160 --- microscope/wavefront_sensors/AndorZyla.py | 970 ------------------ microscope/wavefront_sensors/SID4_SDK.py | 465 ++++----- .../SID4_SDK_FileAnalysis_Example.cpp | 22 +- microscope/wavefront_sensors/SID4_SDK_cffi.py | 80 +- 7 files changed, 279 insertions(+), 1727 deletions(-) delete mode 100644 microscope/wavefront_sensors/AndorSDK3.py delete mode 100644 microscope/wavefront_sensors/AndorSDK3Camera.py delete mode 100644 microscope/wavefront_sensors/AndorZyla.py diff --git a/microscope/devices.py b/microscope/devices.py index 1ec2e08f..a602b277 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -243,7 +243,6 @@ def set_setting(self, name, value): except Exception as err: self._logger.error("in set_setting(%s):" % (name), exc_info=err) - @Pyro4.expose def describe_setting(self, name): """Return ordered setting descriptions as a list of dicts.""" @@ -809,16 +808,7 @@ def _process_data(self, data): - PtoV: peak to valley measurement - zernike_polynomials: a list with the relevant Zernike polynomials """ - - flips = (self._transform[0], self._transform[1]) - rot = self._transform[2] - - # Choose appropriate transform based on (flips, rot). - return {(0, 0): numpy.rot90(data, rot), - (0, 1): numpy.flipud(numpy.rot90(data, rot)), - (1, 0): numpy.fliplr(numpy.rot90(data, rot)), - (1, 1): numpy.fliplr(numpy.flipud(numpy.rot90(data, rot))) - }[flips] + return data # Some measurements related methods # We made the distinction here between ROI (form CameraDevice superclass) diff --git a/microscope/wavefront_sensors/AndorSDK3.py b/microscope/wavefront_sensors/AndorSDK3.py deleted file mode 100644 index e38cb8a6..00000000 --- a/microscope/wavefront_sensors/AndorSDK3.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/python - -################## -# AndorCam.py -# -# Copyright David Baddeley, 2009 -# d.baddeley@auckland.ac.nz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -################## - -import ctypes -import platform - -_stdcall_libraries = {} - -arch, plat = platform.architecture() - -#if 'WinDLL' in dir(): -if plat.startswith('Windows'): - if arch == '32bit': - _stdcall_libraries['ATCORE'] = ctypes.WinDLL('C:/Program Files/Andor SDK3/atcore') - _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('C:/Program Files/Andor SDK3/atutility') - else: - _stdcall_libraries['ATCORE'] = ctypes.OleDLL('C:/Program Files/Andor SDK3/atcore') - _stdcall_libraries['ATUTIL'] = ctypes.OleDLL('C:/Program Files/Andor SDK3/atutility') -else: - _stdcall_libraries['ATCORE'] = ctypes.CDLL('libatcore.so') - _stdcall_libraries['ATUTIL'] = ctypes.CDLL('libatutility.so') - -#### typedefs -AT_H = ctypes.c_int -AT_BOOL = ctypes.c_int -AT_64 = ctypes.c_int64 -AT_U8 = ctypes.c_uint8 -AT_WC = ctypes.c_wchar - -from ctypes import POINTER, c_int, c_uint, c_double - -#### Defines -errorCodes = {} -def errCode(name,value): - errorCodes[value] = name - -AT_INFINITE = 0xFFFFFFFF -AT_CALLBACK_SUCCESS = 0 - -AT_TRUE = 1 -AT_FALSE = 0 - -AT_SUCCESS = 0 -errCode('AT_ERR_NOTINITIALISED', 1) -errCode('AT_ERR_NOTIMPLEMENTED', 2) -errCode('AT_ERR_READONLY', 3) -errCode('AT_ERR_NOTREADABLE', 4) -errCode('AT_ERR_NOTWRITABLE', 5) -errCode('AT_ERR_OUTOFRANGE', 6) -errCode('AT_ERR_INDEXNOTAVAILABLE', 7) -errCode('AT_ERR_INDEXNOTIMPLEMENTED', 8) -errCode('AT_ERR_EXCEEDEDMAXSTRINGLENGTH', 9) -errCode('AT_ERR_CONNECTION', 10) -errCode('AT_ERR_NODATA', 11) -errCode('AT_ERR_INVALIDHANDLE', 12) -errCode('AT_ERR_TIMEDOUT', 13) -errCode('AT_ERR_BUFFERFULL', 14) -errCode('AT_ERR_INVALIDSIZE', 15) -errCode('AT_ERR_INVALIDALIGNMENT', 16) -errCode('AT_ERR_COMM', 17) -errCode('AT_ERR_STRINGNOTAVAILABLE', 18) -errCode('AT_ERR_STRINGNOTIMPLEMENTED', 19) - -errCode('AT_ERR_NULL_FEATURE', 20) -errCode('AT_ERR_NULL_HANDLE', 21) -errCode('AT_ERR_NULL_IMPLEMENTED_VAR', 22) -errCode('AT_ERR_NULL_READABLE_VAR', 23) -errCode('AT_ERR_NULL_READONLY_VAR', 24) -errCode('AT_ERR_NULL_WRITABLE_VAR', 25) -errCode('AT_ERR_NULL_MINVALUE', 26) -errCode('AT_ERR_NULL_MAXVALUE', 27) -errCode('AT_ERR_NULL_VALUE', 28) -errCode('AT_ERR_NULL_STRING', 29) -errCode('AT_ERR_NULL_COUNT_VAR', 30) -errCode('AT_ERR_NULL_ISAVAILABLE_VAR', 31) -errCode('AT_ERR_NULL_MAXSTRINGLENGTH', 32) -errCode('AT_ERR_NULL_EVCALLBACK', 33) -errCode('AT_ERR_NULL_QUEUE_PTR', 34) -errCode('AT_ERR_NULL_WAIT_PTR', 35) -errCode('AT_ERR_NULL_PTRSIZE', 36) -errCode('AT_ERR_NOMEMORY', 37) -errCode('AT_ERR_DEVICEINUSE', 38) - -errCode('AT_ERR_HARDWARE_OVERFLOW', 100) - -class CameraError(Exception): - def __init__(self, fcnName, errNo): - self.errNo = errNo - self.fcnName = fcnName - - def __str__(self): - return 'when calling %s - %s' % (self.fcnName, errorCodes[self.errNo]) - - -#special case for buffer timeout -AT_ERR_TIMEDOUT = 13 -AT_ERR_NODATA = 11 - -class TimeoutError(CameraError): - pass - - - -AT_HANDLE_UNINITIALISED = -1 -AT_HANDLE_SYSTEM = 1 - -### Functions ### -STRING = POINTER(AT_WC) - -#classes so that we do some magic and automatically add byrefs etc ... can classify outputs -class _meta(object): - pass - -class OUTPUT(_meta): - def __init__(self, val): - self.type = val - self.val = POINTER(val) - - def getVar(self, bufLen=0): - v = self.type() - return v, ctypes.byref(v) - -class _OUTSTRING(OUTPUT): - def __init__(self): - self.val = STRING - - def getVar(self, bufLen): - v = ctypes.create_unicode_buffer(bufLen) - return v, v - -OUTSTRING = _OUTSTRING() - -class _OUTSTRLEN(_meta): - def __init__(self): - self.val = c_int - -OUTSTRLEN = _OUTSTRLEN() - - -def stripMeta(val): - if isinstance(val, _meta): - return val.val - else: - return val - -class dllFunction(object): - def __init__(self, name, args = [], argnames = [], lib='ATCORE'): - self.f = getattr(_stdcall_libraries[lib], name) - self.f.restype = c_int - self.f.argtypes = [stripMeta(a) for a in args] - - self.fargs = args - self.fargnames = argnames - self.name = name - - self.inp = [not isinstance(a, OUTPUT) for a in args] - self.in_args = [a for a in args if not isinstance(a, OUTPUT)] - self.out_args = [a for a in args if isinstance(a, OUTPUT)] - - self.buf_size_arg_pos = -1 - for i in range(len(self.in_args)): - if isinstance(self.in_args[i], _OUTSTRLEN): - self.buf_size_arg_pos = i - - ds = name + '\n\nArguments:\n===========\n' - for i in range(len(args)): - an = '' - if i = 0: - bs = args[self.buf_size_arg_pos] - else: - bs = 255 - - for j in range(len(self.inp)): - if self.inp[j]: #an input - ars.append(args[i]) - i+=1 - else: #an output - r, ar = self.fargs[j].getVar(bs) - ars.append(ar) - ret.append(r) - #print r, r._type_ - - res = self.f(*ars) - - if not res == AT_SUCCESS: - if res == AT_ERR_TIMEDOUT or res == AT_ERR_NODATA: - #handle timeouts as a special case, as we expect to get them - raise TimeoutError(self.name, res) - else: - print ars - print res - raise CameraError(self.name, res) - - if len(ret) == 0: - return None - if len(ret) == 1: - return ret[0] - else: - return ret - - - - -def dllFunc(name, args = [], argnames = [], lib='ATCORE'): - f = dllFunction(name, args, argnames, lib) - globals()[name[3:]] = f - -dllFunc('AT_InitialiseLibrary') -dllFunc('AT_FinaliseLibrary') - -dllFunc('AT_Open', [c_int, OUTPUT(AT_H)]) -dllFunc('AT_Close', [AT_H]) - -dllFunc('AT_IsImplemented', [AT_H, STRING, OUTPUT(AT_BOOL)]) -dllFunc('AT_IsReadable', [AT_H, STRING, OUTPUT(AT_BOOL)]) -dllFunc('AT_IsWritable', [AT_H, STRING, OUTPUT(AT_BOOL)]) -dllFunc('AT_IsReadOnly', [AT_H, STRING, OUTPUT(AT_BOOL)]) - -dllFunc('AT_SetInt', [AT_H, STRING, AT_64]) -dllFunc('AT_GetInt', [AT_H, STRING, OUTPUT(AT_64)]) -dllFunc('AT_GetIntMax', [AT_H, STRING, OUTPUT(AT_64)]) -dllFunc('AT_GetIntMin', [AT_H, STRING, OUTPUT(AT_64)]) - -dllFunc('AT_SetFloat', [AT_H, STRING, c_double]) -dllFunc('AT_GetFloat', [AT_H, STRING, OUTPUT(c_double)]) -dllFunc('AT_GetFloatMax', [AT_H, STRING, OUTPUT(c_double)]) -dllFunc('AT_GetFloatMin', [AT_H, STRING, OUTPUT(c_double)]) - -dllFunc('AT_SetBool', [AT_H, STRING, AT_BOOL]) -dllFunc('AT_GetBool', [AT_H, STRING, OUTPUT(AT_BOOL)]) - -dllFunc('AT_SetEnumerated', [AT_H, STRING, c_int]) -dllFunc('AT_SetEnumeratedString', [AT_H, STRING, STRING]) -dllFunc('AT_GetEnumerated', [AT_H, STRING, OUTPUT(c_int)]) -dllFunc('AT_GetEnumeratedCount', [AT_H, STRING, OUTPUT(c_int)]) -dllFunc('AT_IsEnumeratedIndexAvailable', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) -dllFunc('AT_IsEnumeratedIndexImplemented', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) -dllFunc('AT_GetEnumeratedString', [AT_H, STRING, c_int, OUTSTRING, OUTSTRLEN]) - -dllFunc('AT_SetEnumIndex', [AT_H, STRING, c_int]) -dllFunc('AT_SetEnumString', [AT_H, STRING, STRING]) -dllFunc('AT_GetEnumIndex', [AT_H, STRING, OUTPUT(c_int)]) -dllFunc('AT_GetEnumCount', [AT_H, STRING, OUTPUT(c_int)]) -dllFunc('AT_IsEnumIndexAvailable', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) -dllFunc('AT_IsEnumIndexImplemented', [AT_H, STRING, c_int, OUTPUT(AT_BOOL)]) -dllFunc('AT_GetEnumStringByIndex', [AT_H, STRING, c_int, OUTSTRING, OUTSTRLEN]) - -dllFunc('AT_Command', [AT_H, POINTER(AT_WC)]) - -dllFunc('AT_SetString', [AT_H, STRING, STRING]) -dllFunc('AT_GetString', [AT_H, STRING, OUTSTRING, OUTSTRLEN]) -dllFunc('AT_GetStringMaxLength', [AT_H, STRING, OUTPUT(c_int)]) - -dllFunc('AT_QueueBuffer', [AT_H, POINTER(AT_U8), c_int]) -dllFunc('AT_WaitBuffer', [AT_H, OUTPUT(POINTER(AT_U8)), OUTPUT(c_int), c_uint]) -dllFunc('AT_Flush', [AT_H]) - -##################################### -##Utility library (for unpacking etc ...) -dllFunc('AT_InitialiseUtilityLibrary', lib='ATUTIL') -dllFunc('AT_FinaliseUtilityLibrary', lib='ATUTIL') -dllFunc('AT_ConvertBuffer', [POINTER(AT_U8), POINTER(AT_U8), AT_64, AT_64, AT_64, STRING, STRING], lib='ATUTIL') -dllFunc('AT_ConvertBufferUsingMetadata', [POINTER(AT_U8), POINTER(AT_U8), AT_64, STRING], lib='ATUTIL') - -#Initialize the utility library -InitialiseUtilityLibrary() \ No newline at end of file diff --git a/microscope/wavefront_sensors/AndorSDK3Camera.py b/microscope/wavefront_sensors/AndorSDK3Camera.py deleted file mode 100644 index cc43ccfb..00000000 --- a/microscope/wavefront_sensors/AndorSDK3Camera.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/python - -############### -# SDK3Cam.py -# -# Copyright David Baddeley, 2012 -# d.baddeley@auckland.ac.nz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -################ - - -import AndorSDK3 as SDK3 - -class ATProperty(object): - def connect(self, handle, propertyName): - self.handle = handle - self.propertyName = propertyName - - -class ATInt(ATProperty): - def getValue(self): - return SDK3.GetInt(self.handle, self.propertyName).value - - def setValue(self, val): - SDK3.SetInt(self.handle, self.propertyName, val) - - def max(self): - return SDK3.GetIntMax(self.handle, self.propertyName).value - - def min(self): - return SDK3.GetIntMin(self.handle, self.propertyName).value - - -class ATBool(ATProperty): - def getValue(self): - return SDK3.GetBool(self.handle, self.propertyName).value > 0 - - def setValue(self, val): - SDK3.SetBool(self.handle, self.propertyName, val) - - -class ATFloat(ATProperty): - def getValue(self): - return SDK3.GetFloat(self.handle, self.propertyName).value - - def setValue(self, val): - SDK3.SetFloat(self.handle, self.propertyName, val) - - def max(self): - return SDK3.GetFloatMax(self.handle, self.propertyName).value - - def min(self): - return SDK3.GetFloatMin(self.handle, self.propertyName).value - -class ATString(ATProperty): - def getValue(self): - return SDK3.GetString(self.handle, self.propertyName, 255).value - - def setValue(self, val): - SDK3.SetString(self.handle, self.propertyName, val) - - def maxLength(self): - return SDK3.GetStingMaxLength(self.handle, self.propertyName).value - -class ATEnum(ATProperty): - def getIndex(self): - return SDK3.GetEnumIndex(self.handle, self.propertyName).value - - def setIndex(self, val): - SDK3.SetEnumIndex(self.handle, self.propertyName, val) - - def getString(self): - return self.__getitem__(self.getIndex()) - - def setString(self, val): - SDK3.SetEnumString(self.handle, self.propertyName, val) - - def __len__(self): - return SDK3.GetEnumCount(self.handle, self.propertyName).value - - def __getitem__(self, key): - return SDK3.GetEnumStringByIndex(self.handle, self.propertyName, key, 255).value - - def getAvailableValues(self): - n = SDK3.GetEnumCount(self.handle, self.propertyName).value - - return [SDK3.GetEnumStringByIndex(self.handle, self.propertyName, i, 255).value for i in range(n) if SDK3.IsEnumIndexAvailable(self.handle, self.propertyName, i).value] - -class ATCommand(ATProperty): - def __call__(self): - return SDK3.Command(self.handle, self.propertyName) - -class camReg(object): - #keep track of the number of cameras initialised so we can initialise and finalise the library - numCameras = 0 - - @classmethod - def regCamera(cls): - if cls.numCameras == 0: - SDK3.InitialiseLibrary() - - cls.numCameras += 1 - - @classmethod - def unregCamera(cls): - cls.numCameras -= 1 - if cls.numCameras == 0: - SDK3.FinaliseLibrary() - -#make sure the library is intitalised -camReg.regCamera() - -def GetNumCameras(): - return SDK3.GetInt(SDK3.AT_HANDLE_SYSTEM, 'DeviceCount').value - -def GetSoftwareVersion(): - return SDK3.GetString(SDK3.AT_HANDLE_SYSTEM, 'SoftwareVersion', 255) - -class SDK3Camera(object): - def __init__(self, camNum): - '''camera initialisation - note that this should be called from derived classes - *AFTER* the properties have been defined''' - #camReg.regCamera() #initialise the library if needed - self.camNum = camNum - - - def Init(self): - self.handle = SDK3.Open(self.camNum) - self.connectProperties() - - - def connectProperties(self): - for name, var in self.__dict__.items(): - if isinstance(var, ATProperty): - var.connect(self.handle, name) - - def GetSerialNumber(self): - return SDK3.GetString(self.handle, 'SerialNumber', 64) - - - def shutdown(self): - SDK3.Close(self.handle) - SDK3.FinaliseLibrary() - print('Camera Closed and Andor library finalized.') - camReg.unregCamera() ## TODO: do we have to unregister? - - diff --git a/microscope/wavefront_sensors/AndorZyla.py b/microscope/wavefront_sensors/AndorZyla.py deleted file mode 100644 index 8cb4d13e..00000000 --- a/microscope/wavefront_sensors/AndorZyla.py +++ /dev/null @@ -1,970 +0,0 @@ -#!/usr/bin/python - -############### -# AndorNeo.py -# -# Copyright David Baddeley, 2012 -# d.baddeley@auckland.ac.nz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -################ - - -from AndorSDK3Camera import * -import numpy as np -import threading -import Queue -import Pyro4 -# Config Pyro4 to use pickle as serializer -Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') -Pyro4.config.SERIALIZERS_ACCEPTED.remove('serpent') -Pyro4.config.SERIALIZER = 'pickle' -import time -import traceback -import gc -import atexit - -import memoryHandler -# import ctypes -# import os - - -## Some configuration parameters - -# Needed to keep the daemon from only listening to requests originating from the local host. -MY_IP_ADDRESS = '10.6.19.30' - -# CameraNumber is by default 0, the first non sumulated camera -CAMERA_NUMBER = 0 - -# Memory we want to allocate for the camera buffers in Mb -MEMORY_ALLOCATION = 1000 - -# Cropping modes. We keep these presets for convenience. -croppingModes = ('CROP_FULL', # 0 - 'CROP_1024', # 1 - 'CROP_512', # 2 - 'CROP_256', # 3 - 'CROP_128', # 4 - 'CROP_ARBITRARY' # 5 - ) - -croppingModesSizes = {'CROP_FULL': (2048,2048), # TODO: implement this depending on camera model - 'CROP_1024': (1024,1024), - 'CROP_512': (512,512), - 'CROP_256': (256,256), - 'CROP_128': (128,128), - 'CROP_ARBITRARY': (2048,2048) - } - -# Binning modes -BINNING_MODES = (u'1x1', u'2x2', u'3x3', u'4x4', u'8x8') -BINNING_VALUES = (1, 2, 3, 4, 8) - -# Trigger modes -# TODO: Should get this directly from the camera -(TRIGGER_INTERNAL, TRIGGER_SOFTWARE, TRIGGER_EXTERNAL, TRIGGER_EXTERNAL_START, TRIGGER_EXTERNAL_EXPOSURE) = (1, 4, 6, 2, 3) - -# Pixel encodings -PIXEL_ENCODING_MODES = ['Mono12', 'Mono12Packed', 'Mono16', 'Mono32'] - -# Acquisition Modes -(MODE_FIXED, MODE_CONTINUOUS) = (0, 1) - -# A black image -STATIC_BLACK = np.zeros((512, 512), dtype = np.uint16) # TODO: verify this is used - - -## Some helper classes and functions - -def resetCam(func): - ''' - Decorator function to put the camera in a mode where it can be - interacted with. Mostly needed to stop acquisitions. - ''' - def wrappedFunc(inst, *args, **kwargs): - inst.stopAcquisition() - inst.TriggerMode.setString(u'Internal') - inst.CycleMode.setString(u'Fixed') - inst.FrameCount.setValue(1) - - func(inst, *args, **kwargs) - -# print(func.__name__) -# print(args) -# print('Trigger: ' + str(inst.getTrigger())) - - -# # Start the acquisition with external triggers -# if inst.currentTrigger not in (TRIGGER_INTERNAL, TRIGGER_SOFTWARE): -# inst.startAcquisition() - - return wrappedFunc - - -class DataThread(threading.Thread): - ''' - This class retrieves images from the camera, and sends them to our client. - ''' - def __init__(self, parent, width, height): - threading.Thread.__init__(self) - - ## Loop back to parent to be able to communicate with it. - self.parent = parent - - ## Image dimensions, which we need for when we retrieve image - # data. Our parent is responsible for updating these for us. - self.width = self.height = 0 - ## Lock on modifying the above. - self.sensorLock = threading.Lock() - - ## Connection to client - self.clientConnection = None - - ## Whether or not we should unload images from the camera - self.shouldSendImages = True - - ## Whether the Datathread amin loop should run or not: - self.shouldRun = True - - ## Initial timestamp that we will use in conjunction with time.clock() - # to generate high-time-resolution timestamps. Just using time.time() - # straight-up on Windows only has accuracy of ~15ms. - self.initialTimestamp = time.time() + time.clock() - - ## Offset image array to subtract off of each image we receive. - self.offsetImage = None - - ## Pull images from self.imageQueue and send them to the client. - def run(self): - count = 0 - gTime = None - getTime = 0 - fixTime = 0 - sendTime = 0 - while self.shouldRun: - # This will block indefinitely until images are available. - with self.sensorLock: - try: - start = time.clock() - image = self.parent.getImage() - getTime += (time.clock() - start) - except Exception, e: - if 'getImage failed' not in e: - print "Error in getImage:",e - # Probably a timeout; just try again. - continue - # \todo This timestamp is potentially bogus if we get behind in - # processing images. - timestamp = time.clock() + self.initialTimestamp - start = time.clock() - image = self.fixImage(image) - fixTime += time.clock() - start - count += 1 - if count % 100 == 0: - # Periodically manually invoke the garbage collector, to - # ensure that we don't build up a giant pile of work that - # would interfere with our average write speed. - if gTime is None: - gTime = time.time() - delta = time.time() - gTime - gTime = time.time() - getTime = fixTime = sendTime = 0 - gc.collect() - - if image is not None and self.shouldSendImages and self.clientConnection is not None: - try: - start = time.clock() - self.clientConnection.receiveData('new image', image, timestamp) - cost = time.clock() - start - if cost > .5: - print "Took %.2fs to send to client" % cost - sendTime += cost - except Exception, e: - print "Failed to send image to client: %s", e - traceback.print_exc() - elif image is not None and self.shouldSendImages and self.clientConnection is None: # For debugging we want to print if we have an image - print 'Acquired Image:' - print image[:10] - - ## Fix an image -- set its shape and apply any relevant correction. - def fixImage(self, image): - # image.shape = self.height, self.width - if self.offsetImage is not None and self.offsetImage.shape == image.shape: - # Apply offset correction. - image -= self.offsetImage - return image - - ## Update who we send image data to. - def setClient(self, connection): - self.clientConnection = connection - - ## Update our image dimensions. - def setImageDimensions(self, width, height): - with self.sensorLock: - self.width = width - self.height = height - - ## Update the image we use for offset correction. - def setOffsetCorrection(self, image): - self.offsetImage = image - - ## Retrieve our offset correction image. - def getOffsetCorrection(self): - return self.offsetImage - - ## Stop method to exit the Datathread - def stop(self): - self.shouldRun = False - -class AndorBase(SDK3Camera): - - def __init__(self, camNum): - # define properties - self.CameraAcquiring = ATBool() # Returns whether or not an acquisition is currently acquiring. - self.SensorCooling = ATBool() # Configures the state of the sensor cooling. Cooling is disabled by default at power up and must be enabled for the camera to achieve its target temperature. The actual target temperature can be set with the TemperatureControl feature where available. - - self.AcquisitionStart = ATCommand() # Starts an acquisition - self.AcquisitionStop = ATCommand() # Stops an acquisition - - self.CameraPresent = ATBool() # Returns whether the camera is connected to the system. Register a callback to this feature to be notified if the camera is disconnected. Notification of disconnection will not occur if CameraAcquiring is true, in this case AT_WaitBuffer will return an error. - - self.CycleMode = ATEnum() # Configures whether the camera will acquire a fixed length sequence or a continuous sequence. In Fixed mode the camera will acquire FrameCount number of images and then stop automatically. In Continuous mode the camera will continue to acquire images indefinitely until the AcquisitionStop command is issued. - self.ElectronicShutteringMode = ATEnum() # Configures which on-sensor electronic shuttering mode is used - self.FanSpeed = ATEnum() # Configures the speed of the fan in the camera - self.PixelEncoding = ATEnum() # Configures the format of data stream. - self.PixelReadoutRate = ATEnum() # Configures the rate of pixel readout from the sensor. - self.TriggerMode = ATEnum() # Allows the user to configure the camera trigger mode at a high level. If the trigger mode is set to Advanced then the Trigger Selector and Trigger Source feature must also be set. - -# self.IsImplemented = ATBool() # Indicates whether the camera has implemented the feature specified. -# self.IsReadable = ATBool() # Indicates whether the feature specified can currently be read. -# self.IsWritable = ATBool() # Indicates whether the feature specified can currently be written. -# self.IsReadOnly = ATBool() # Indicates whether the feature specified can be modified. - - self.AOIHeight = ATInt() # Configures the Height of the sensor area of interest in super-pixels. - self.AOILeft = ATInt() # Configures the left hand coordinate of the sensor area of interest in sensor pixels. - self.AOITop = ATInt() # Configures the top coordinate of the sensor area of interest in sensor pixels. - self.AOIWidth = ATInt() # Configures the Width of the sensor area of interest in super-pixels. - self.PixelHeight = ATFloat() # Returns the height of each pixel in micrometers. - self.PixelWidth = ATFloat() # Returns the width of each pixel in micrometers. - - self.AOIHBin = ATInt() # Configures the Horizontal Binning of the sensor area of interest. - self.AOIVBin = ATInt() # Configures the Vertical Binning of the sensor area of interest. - self.AOIBinning = ATEnum() # Sets up pixel binning on the camera. Options: 1x1, 2x2, 3x3, 4x4, 8x8 - - self.FrameCount = ATInt() # Configures the number of images to acquire in the sequence. The value of FrameCount must be any value which is a multiple of AccumulateCount. This ensures the accumulation contains the correct number of frames. When this feature is unavailable then the camera does not currently support fixed length series, therefore you must explicitly abort the acquisition once you have acquired the amount of frames required. - self.ImageSizeBytes = ATInt() # Returns the buffer size in bytes required to store the data for one frame. This will be affected by the Area of Interest size, binning and whether metadata is appended to the data stream. - self.SensorHeight = ATInt() # Returns the height of the sensor in pixels. - self.SensorWidth = ATInt() # Returns the width of the sensor in pixels. - - self.CameraModel = ATString() # Returns the camera model - self.SerialNumber = ATString() # Returns the camera serial number - - self.ExposureTime = ATFloat() # The requested exposure time in seconds. Note: In some modes the exposure time can also be modified while the acquisition is running - self.FrameRate = ATFloat() # Configures the frame rate in Hz at which each image is acquired during any acquisition sequence. This is the rate at which frames are acquired by the camera which may be different from the rate at which frames are delivered to the user. For example when AccumulateCount has a value other than 1, the apparent frame rate will decrease proportionally. - self.SensorTemperature = ATFloat() # Read the current temperature of the sensor. - - #end auto properties - - - # Initialize the camera - SDK3Camera.__init__(self, camNum) - SDK3Camera.Init(self) - - # cache some properties that we have to access regularly or without - # interfering with the camera - self.serialNumber = self.SerialNumber.getValue() - self.cameraModel = self.CameraModel.getValue() - - self.CCDWidth = self.SensorWidth.getValue() - self.CCDHeight = self.SensorHeight.getValue() - - self.width = self.AOIWidth.getValue() - self.height = self.AOIHeight.getValue() - self.currentAOIHBin = 1 - self.currentAOIVBin = 1 - - self.currentTrigger = self.TriggerMode.getIndex() - self.currentElectronicShutteringMode = self.ElectronicShutteringMode.getIndex() - self.currentExposureTime = self.ExposureTime.getValue() - self.currentFrameRate = self.FrameRate.getValue() - self.currentFrameCount = self.FrameCount.getValue() - self.currentAcquisitionMode = self.CycleMode.getIndex() - self.currentPixelReadoutRate = self.PixelReadoutRate.getString() - self.currentPixelEncoding = self.PixelEncoding.getString() - - # Set the cropMode - self.curCropMode = 0 - - - - # Set some of the default camera settings - self.SensorCooling.setValue(True) -# self.setCrop(2) - self.setPixelEncoding(PIXEL_ENCODING_MODES.index('Mono16')) - - # Print some of the camera infos - print('Camera model: ' + self.getCameraModel()) - print('Camera serial number: ' + self.getSerialNumber()) - print('CCD sensor shape: ' + str(self.width) + 'x' + str(self.height)) - print('Pixel encoding: ' + self.getPixelEncoding()) - print('Shutter mode: ' + str(self.getElectronicShutteringMode())) - print('Fan speed: ' + self.FanSpeed.getString()) - print('Sensor cooling: ' + str(self.SensorCooling.getValue())) - print('Sensor temperature: ' + str(self.SensorTemperature.getValue())) - - # Create a memoryHandler instance to manage the camera buffers - self.memoryHandler = memoryHandler.MemoryHandler(self.handle) - - # Create a DataThread instance to manage the images transfer to the client - self.dataThread = DataThread(self, self.width, self.height) - - self.dataThread.start() - - ## Some methods to manage memory - - def allocMemory(self, timeout = 1000): - ''' - Allocate memory to store images - timeout is in millisec - ''' - strides = self.getStrides() - imageBytes = self.ImageSizeBytes.getValue() - - # We allocate about 500MB of RAM to the image buffer. Allocating - # too much memory seems to cause slowdowns and crashes, oddly enough. - numBuffers = (MEMORY_ALLOCATION * 1024 * 1024) / imageBytes - - print('Allocating memory for:') # TODO: remove this - print('numBuffers: ' + str(numBuffers)) - print('imageBytes: ' + str(imageBytes)) - print('imageWidth: ' + str(self.width)) - print('imageHeight: ' + str(self.height)) - print('strides: ' + str(strides)) - - self.memoryHandler.allocMemory(numBuffers = numBuffers, - imageBytes = imageBytes, - imageWidth = self.width, - imageHeight = self.height, - strides = strides, - timeout = timeout - ) - - def getStrides(self): - ''' - Returns the strides on the images array. - This is the behavior for a default SimCam and must be overridden in a - hardware camera. - ''' - return self.width * 16 # TODO: Not sure about this - - - ## Some methods to manage data transfer - - def receiveClient(self, uri): - ''' - Get told who we should be sending image data to, and how we - should mark ourselves when we do. - ''' - print('Receiving new client ' + str(uri)) - if uri is None: - self.dataThread.setClient(None) - self.dataThread.shouldSendImages = False - else: - connection = Pyro4.Proxy(uri) - connection._pyroTimeout = 5 - self.dataThread.setClient(connection) - self.dataThread.shouldSendImages = True - - def startAcquisition(self): - ''' - Starts the acquisition - ''' - # Make sure that camera is not acquiring - if not self.CameraAcquiring.getValue(): - try: - self.AcquisitionStart() - except Exception as e: - print('Could not start acquisition') - print(e) - - def stopAcquisition(self): - ''' - Stops the acquisition - ''' - if self.CameraAcquiring.getValue(): - try: - self.AcquisitionStop() - except Exception as e: - print('Could not stop acquisition') - print(e) - - def goSilent(self): - ''' - Stop sending images to the client, even if we still - receive them. - ''' - print "Switching to quiet mode" - with self.dataThread.sensorLock: - self.dataThread.shouldSendImages = False - - def goLoud(self): - ''' - Start sending new images to the client again. - ''' - print "Switching to loud mode" - with self.dataThread.sensorLock: - self.dataThread.shouldSendImages = True - - def getImage(self, timeout = .5): - ''' - get a single image wrapped around the memoryHandler getImage - ''' - return self.memoryHandler.getImage(timeout) - - def getImages(self, numImages, timeout = .5): - ''' - Retrieve the specified number of images. This may fail - if we ask for more images than we have, so the caller - should be prepared to catch exceptions. - ''' - result = [] - for i in xrange(numImages): - image = self.getImage(timeout) - result.append(image) - return result - - def discardImages(self, numImages = None): - ''' - Discard the specified number of images from the queue, - defaulting to all of them. - ''' - count = 0 - while count != numImages: - try: - self.getImage(0) - count += 1 - except Exception, e: - # Got an exception, probably because the - # queue is empty, so we're all done here. - return - - ## Camera methods - - @resetCam - def setTrigger(self, triggerMode): - ''' - Changes the triggering mode of the camera - ''' - try: - self.TriggerMode.setIndex(triggerMode) - except Exception as e: - print('Could not change the trigger mode:') - print e - return - - self.currentTrigger = triggerMode - - # Start the acquisition with external triggers - if triggerMode not in (TRIGGER_INTERNAL, TRIGGER_SOFTWARE): - self.CycleMode.setIndex(MODE_CONTINUOUS) - self.startAcquisition() - - - def getTrigger(self): - ''' - Returns the triggering mode of the camera - ''' - return self.currentTrigger - - def getTriggerString(self): - ''' - Returns the current trigger mode as a string - ''' - return self.TriggerMode.getString() - - @resetCam - def setElectronicShutteringMode(self, isGlobal): - ''' - Changes the shutter mode.0 is rolling shutter; 1 is global - ''' - try: - self.ElectronicShutteringMode.setIndex(isGlobal) - except Exception, e: - print('Could not change Electronic shuttering mode') - print(e) - return - - self.currentElectronicShutteringMode = isGlobal - - def getElectronicShutteringMode(self): - ''' - Get the current shutter mode. 0 is rolling shutter; 1 is global - ''' - return self.currentElectronicShutteringMode - - @resetCam # Seems that exposure time can be changed without stopping acquisition - def setExposureTime(self, time): - ''' - Changes the exposure time in the camera. In seconds - ''' - try: - self.ExposureTime.setValue(time) - except Exception as e: - print('ExposureTime could not be set:') - print(e) - return - - self.currentExposureTime = time - - def getExposureTime(self): - ''' - Returns the exposure time of the camera. In seconds - ''' - return self.currentExposureTime - - def getMinExposureTime(self): - ''' - Returns the minimum exposure time accepted by the camera. In seconds - ''' - return self.ExposureTime.min() - - def setCrop(self, mode): - ''' - Set the cropping mode to one of a few presets. - In version 2 of the SDK, these were the only valid crop modes; in - version 3, we can set any crop mode we want, but these - presets are still convenient. - mode is an integer. - ''' - self.setCropSize(croppingModesSizes[croppingModes[mode]]) - self.curCropMode = mode - - def setCropSize(self, cropSize, binning = 0): - ''' - Changes the AOI in the camera. - - cropSize is a tupple or list of two integers providing the size of the AOI (x, y). - binning must be a string - AOI will be centered in the camera - ''' - # cropSize must be converted into superpixel size in case there is binning - binnedCropSize = cropSize - if binning != 0: - binnedCropSize[0] = cropSize[0] // BINNING_VALUES[binning] - binnedCropSize[1] = cropSize[1] // BINNING_VALUES[binning] - - self.setCropArbitrary(binnedCropSize[0], - ((self.CCDWidth - cropSize[0]) // 2) + 1, - binnedCropSize[1], - ((self.CCDHeight - cropSize[1]) // 2) + 1, - binning, - ) - - @resetCam - def setCropArbitrary(self, width, left, height, top, binning = 0): - ''' - Changes arbitrarily the camera AOI - ''' - try: - # Set binning - self.AOIBinning.setIndex(binning) - - # Set AOI - self.AOIWidth.setValue(width) - self.AOILeft.setValue(left) - self.AOIHeight.setValue(height) - self.AOITop.setValue(top) - except Exception as e: - print('Could not change cropping mode') - print(e) - return - - # update width and height values - self.width = width - self.height = height - self.dataThread.setImageDimensions(width, height) - - # reallocate memory - self.allocMemory() - - # TODO: understand this - # TODO: should we return here the time shift for a pseudo global shuttering? - stride = self.getStrides() - div = float(stride) / width - return (stride, width, int(div) == div) - - def getCropMode(self): - return self.curCropMode - - def getImageShape(self): - ''' - Returns the image size (AOI) as a tupple of two integers (x, y) - ''' - return (self.width, self.height) - - def setOffsetCorrection(self, image): - ''' - Set an offset correction image to use. - ''' - self.dataThread.setOffsetCorrection(image) - - def getIsOffsetCorrectionOn(self): - ''' - Return true if a correction file is loaded for the current image dimensions. - ''' - correction = self.dataThread.getOffsetCorrection() - return correction is not None and correction.shape == (self.height, self.width) - - def getSerialNumber(self): - return self.serialNumber - - def getCameraModel(self): - return self.cameraModel - - @resetCam - def setIntegTime(self, iTime): - self.setExposureTime(iTime*1e-3) - self.setFrameRate(self.FrameRate.max()) - - def getIntegTime(self): - return self.currentExposureTime - - def getCycleTime(self): - return 1.0/self.currentFrameRate - - @resetCam - def setAcquisitionMode(self, mode): - try: - self.CycleMode.setIndex(mode) - except Exception as e: - print('Could not change AcquisitionMode (cycleMode):') - print(e) - return - - self.currentAcquisitionMode = mode - - def getAcquisitionMode(self): - return self.currentAcquisitionMode - - @resetCam - def setFrameRate(self, frameRate): - try: - self.FrameRate.setValue(frameRate) - except Exception as e: - print('Could not change the frame rate:') - print(e) - return - - self.currentFrameRate = frameRate - - def getFrameRate(self): - return self.currentFrameRate - - def getFrameCount(self): - return self.currentFrameCount - - @resetCam - def setFrameCount(self, frameCount): - ''' - ''' - try: - self.FrameCount.setValue(frameCount) - except Exception as e: - print('Could not change frame Count:') - print(e) - return - - self.currentFrameCount = frameCount - - @resetCam - def setPixelReadoutRate(self, rate): - ''' - Sets the PixelReadoutRate. - rate must be a string. For sCMOS: '280 MHz' or '100 MHz'. - For the SimCam: '550 MHz' - ''' - try: - self.PixelReadoutRate.setString(rate) - except Exception as e: - print('Could not change the pixel readout rate:') - print(e) - return - - self.currentPixelReadoutRate = rate - - def getPixelReadoutRate(self): - ''' - Returns the pixel readout rate as a string - ''' - return self.currentPixelReadoutRate - - @resetCam - def setPixelEncoding(self, index): - ''' - Sets the pixel encoding of the camera: - ''' - try: - self.PixelEncoding.setIndex(index) - except Exception as e: - print('Could not change the pixel encoding:') - print(e) - return - - self.currentPixelEncoding = PIXEL_ENCODING_MODES[index] - - def getPixelEncoding(self): - return self.currentPixelEncoding - - def getCCDWidth(self): - return self.CCDWidth - - def getCCDHeight(self): - return self.CCDHeight - - @resetCam - def setHorizBin(self, horizontalBinning): - try: - self.AOIHBin.setValue(horizontalBinning) - except Exception as e: - print('Could not change the horizontal Binning:') - print(e) - return - - self.currentAOIHBin = horizontalBinning - - def getHorizBin(self): - return self.currentAOIHBin - - @resetCam - def setVertBin(self, verticalBinning): - try: - self.AOIVBin.setValue(verticalBinning) - except Exception as e: - print('Could not change the vertical Binning:') - print(e) - return - - self.currentAOIVBin = verticalBinning - - def getVertBin(self): - return self.currentAOIVBin - - def getSensorTemperature(self): - '''for some reason querying the temperature takes a lot of time - Do it less often - ''' - return self.SensorTemperature.getValue() - - def getTemperatureStatus(self): - ''' - returns the status of the temperature sensor - ''' - return self.TemperatureStatus.getString() - - def isReady(self): - return True # TODO: implement this - - def getAOIWidth(self): - return self.width - - def getAOIHeight(self): - return self.height - - def abort(self): - ''' - Stop acquiring Images - ''' - self.stopAcquisition() - - def Shutdown(self): - self.abort() - print 'Going silent' - self.goSilent() - print 'Joining Datathread' - self.dataThread.stop() - self.dataThread.join() - print 'Stopping Memoryhandler' - self.memoryHandler.stop() - print 'Shutting down camera' - self.shutdown() - - def __del__(self): - self.Shutdown() - #self.compT.kill = True - - -class AndorZyla(AndorBase): - def __init__(self, camNum): - - #define properties - self.AOIStride = ATInt() # The size of one row in the image in bytes. Extra padding bytes may be added to the end of each line after pixel data to comply with line size granularity restrictions imposed by the underlying hardware interface. - - self.Baseline = ATInt() # Returns the baseline level of the image with current settings - - self.CameraName = ATString() # Returns the name of the camera. - - self.Overlap = ATBool() # Enables overlap readout mode. - self.SpuriousNoiseFilter = ATBool() # Enables or Disables the Spurious Noise Filter - self.StaticBlemishCorrection = ATBool() # Enables or Disables Static Blemish Correction - - self.VerticallyCentreAOI = ATBool() # Vertically centres the AOI in the frame. With this enabled, AOITop will be disabled. - - self.CameraDump = ATCommand() # Dumps current hardware configuration information to file in the executable directory. File is called camdump-Serial Number - self.SoftwareTrigger = ATCommand() # Generates a software trigger in the camera. Used to generate each frame on the camera whenever the trigger mode is set to Software. - - self.ExternalTriggerDelay = ATFloat() # Sets the delay time between the camera receiving an external trigger and the acquisition start. - self.FastAOIFrameRateEnable = ATBool() # Enables faster framerates at small AOIs. - self.RollingShutterGlobalClear = ATBool() # Enables Rolling Shutter Global Clear readout mode. - self.RowReadTime = ATFloat() # Configures the time in seconds to read a single row. - self.SensorReadoutMode = ATEnum() # Configures the direction in which the sensor will be read out - self.ShutterOutputMode = ATEnum() # Controls the mode the external trigger will run in. External Shutter signal can either be set to high (open) or low (closed). ShutterOutput can be triggered by setting AuxOutSourceTwo to ExternalShutterControl - - self.TemperatureStatus = ATEnum() # Reports the current state of cooling towards the Target Sensor Temperature. Read Only - self.SimplePreAmpGainControl = ATEnum() # Wrapper Feature to simplify selection of the sensitivity and dynamic range options. This feature should be used as a replacement for the PreAmpGainControl feature as some of the options in the PreAmpGainControl feature are not supported on all cameras. Supported Bit Depth will be dependent on the camera - self.BitDepth = ATEnum() # Returns the number bits used to store information about each pixel of the image. Supported Bit Depth will be dependent on the camera. - self.MetadataEnable = ATBool() # Enable metadata. This is a global flag which will enable inclusion of metadata in the data stream as described in Section 4.5 Metadata. When this flag is enabled the data stream will always contain the MetadataFrame information. This will override the subsequent metadata settings when disabled. - self.MetadataFrame = ATBool() # Indicates whether the MetadataFrame information is included in the data stream. This is read only and is automatically sent if metadata is enabled. - self.MetadataTimestamp = ATBool() # Enables inclusion of timestamp information in the metadata stream. The timestamp indicates the time at which the exposure for the frame started. - - self.ReadoutTime = ATFloat() # This feature will return the time to readout data from a sensor. - self.ExposedPixelHeight = ATInt() # Configures the exposure window in pixels. - - self.TimestampClock = ATInt() # Reports the current value of the camera internal timestamp clock. This same clock is used to timestamp images as they are acquired when the MetadataTimestamp feature is enabled. The clock is reset to zero when the camera is powered on and then runs continuously at the frequency indicated by the TimestampClockFrequency feature. The clock is 64-bits wide. - self.TimestampClockFrequency = ATInt() # Reports the frequency of the camera internal timestamp clock in Hz. - self.TimestampClockReset = ATCommand() # Resets the camera internal timestamp clock to zero. As soon as the reset is complete the clock will begin incrementing from zero at the rate given by the TimestampClockFrequency feature. - - self.AccumulateCount = ATInt() # Sets the number of images that should be summed to obtain each image in sequence. - self.Baseline = ATInt() # Returns the baseline level of the image with current settings - self.LUTIndex = ATInt() # Sets the position in the LUT to read/write a new pixel map - self.LUTValue = ATInt() # Sets the value in LUT in position specified by LUT Index - - self.ControllerID = ATString() # Returns a unique identifier for the camera controller device. i.e. Frame grabber over Cameralink - self.FirmwareVersion = ATString() # Returns the camera firmware version - - - AndorBase.__init__(self,camNum) - - def Init(self): - ''' - Will open the camera connection and set some default parameters - ''' - AndorBase.Init(self) - - print('Temperature status: ' + self.getTemperatureStatus()) - print('FirmwareVersion: ' + self.FirmwareVersion.getValue()) - print('Baseline level: ' + str(self.Baseline.getValue())) - - # Configure default camera status - self.setFanSpeed(u'Off') # TODO: get this from some configuration file - self.setSimplePreAmpGainControl(u'16-bit (low noise & high well capacity)') - self.setPixelReadoutRate(u'280 MHz') - - # store some values - self.currentAccumulateCount = self.AccumulateCount.getValue() - self.currentSimplePreAmpGainControl = self.SimplePreAmpGainControl.getString() - - # Define Zyla specific methods - - def getAccumulateCount(self): - return self.currentAccumulateCount - - @resetCam - def setAccumulateCount(self, nrOfFrames = 1): - try: - self.AccumulateCount.setValue(nrOfFrames) - except Exception as e: - print('Could not change accumulate count:') - print(e) - return - - self.currentAccumulateCount = nrOfFrames - - def getReadoutTime(self): - ''' - Returns the readout time in seconds as a float - ''' - return self.ReadoutTime.getValue() - - def getStrides(self): - ''' - Returns the strides on the images array. - This is the bahaviour for a default SimCam and must be overridden in a - hardware camera. - ''' - return self.AOIStride.getValue() - - def setFanSpeed(self, speed): - ''' - Sets the fan speed. For Zyla speed is 'On' or 'Off' - ''' - try: - self.FanSpeed.setString(speed) - except Exception as e: - print('Could not change the fan speed:') - print(e) - - @resetCam - def setSimplePreAmpGainControl(self, stringValue): - ''' - Sets the sensitivity and dynamic range options - stringValue is: - 11-bit (high well capacity) - 12-bit (high well capacity) - 11-bit (low noise) - 12-bit (low noise) - 16-bit (low noise & high well capacity) - ''' - try: - self.SimplePreAmpGainControl.setString(stringValue) - except Exception as e: - print('Could not change the preAmp gain control:') - print(e) - return - - self.currentSimplePreAmpGainControl = stringValue - -class AndorSim(AndorBase): - def __init__(self, camNum): - #define properties - self.SynchronousTriggering = ATBool() # Configures whether external triggers are synchronous with the read out of a sensor row. Asynchronous triggering may result in data corruption in the row being digitised when the triggers occurs. - - self.PixelCorrection = ATEnum() # Configures the pixel correction to be applied. - self.TriggerSelector = ATEnum() # Only if trigger mode in advanced - self.TriggerSource = ATEnum() # Only if trigger mode in advanced - - - AndorBase.__init__(self,camNum) - -if __name__ == '__main__': - try: - cam = AndorZyla(camNum = CAMERA_NUMBER) - daemon = Pyro4.Daemon(port = 7000, host = MY_IP_ADDRESS) - Pyro4.Daemon.serveSimple( - { - cam: 'Andorcam', - }, - daemon = daemon, ns = False, verbose = True - ) - - except Exception, e: - traceback.print_exc() - - def exitRemoteCamera(): - daemon.Daemon.close(self) - cam.Shutdown() - - atexit.register(exitRemoteCamera()()) - diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 6abf8e8c..e04c4707 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -75,13 +75,11 @@ 'AE_On/Off': (8, -1, -1) } -INVALIDATES_BUFFERS = ['_simple_pre_amp_gain_control', '_pre_amp_gain_control', +INVALIDATES_SETTINGS = ['_simple_pre_amp_gain_control', '_pre_amp_gain_control', '_aoi_binning', '_aoi_left', '_aoi_top', '_aoi_width', '_aoi_height', ] -# We setup some necessary configuration parameters -# TODO: Move this into the config file - +# We setup some necessary configuration parameters. TODO: Move this into the config file # This is the default profile path of the camera WFS_PROFILE_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' @@ -100,10 +98,14 @@ class SID4Device(WavefrontSensorDevice): the SID4_SDK.h file with some modifications. Namely all #include and #ifdef have been removed. Also, the typedef of LVBoolean has been changed for 'unsigned char' - :param header_defs: Absolute path to the header definitions from the SDK. - :param dll_path: name of, or absolute path to the SID4_SDK.dll file + :param header_definitions: Absolute path to the header definitions from the SDK. + :param sid4_sdk_dll_path: name of, or absolute path to, the SID4_SDK.dll file + :param zernike_sdk_dll_path: name of, or absolute path to, the Zernike_SDK.dll file :param wfs_profile_path: Absolute path to the profile file that has to be loaded at startup. Must be a byte encoded string. + :param camera_attributes: This is a dictionary containing a reference to the camera attributes + returned by the SDK's 'Camera_GetAttributeList'. The dictionary has as keys the attribute name + and as values a tuple containing (attribute_id, min_value, max_value). """ def __init__(self, header_definitions=HEADER_DEFINITIONS, @@ -170,8 +172,8 @@ def __init__(self, self.camera_sn = self.ffi.new("char[]", self.buffer_size) self.camera_sn_bs = self.ffi.cast("long", self.buffer_size) - self.camera_array_size = self.ffi.new('ArraySize *') - self.analysis_array_size = self.ffi.new('ArraySize *') + self.camera_array_size = self.ffi.new("ArraySize *") + self.analysis_array_size = self.ffi.new("ArraySize *") # Create zernike-related parameters self.zernike_information = self.ffi.new("ZernikeInformation *") @@ -196,11 +198,12 @@ def __init__(self, self.buffer_size, readonly=True) self.add_setting('user_profile_file', 'str', - lambda: self.ffi.string(self.user_profile_file), # TODO: this lambda will probably not work - self._set_user_profile_file, - self.buffer_size) + lambda: self.ffi.string(self.user_profile_file), + None, + self.buffer_size, + readonly=True) self.add_setting('user_profile_description', 'str', - lambda: self.ffi.string(self.user_profile_description), + lambda: self.ffi.string(self.user_profile_description), # TODO: Fix here None, self.buffer_size, readonly=True) @@ -219,10 +222,15 @@ def __init__(self, None, self.buffer_size, readonly=True) + self.add_setting('camera_sn', 'str', + lambda: self.ffi.string(self.camera_sn), + None, + self.buffer_size, + readonly=True) # Add camera settings self.add_setting('frame_rate', 'enum', - lambda: FRAME_RATES[self.camera_information.FrameRate], + self._get_frame_rate, self._set_frame_rate, lambda: FRAME_RATES.keys()) self.add_setting('trigger_mode', 'enum', @@ -286,22 +294,22 @@ def __init__(self, self._set_remove_background_image, None) self.add_setting('phase_size_width', 'int', - lambda: self.analysis_information.PhaseSize_width, + self._get_phase_size_width, None, (0, 160), readonly=True) self.add_setting('phase_size_height', 'int', - lambda: self.analysis_information.PhaseSize_Height, + self._get_phase_size_height, None, (0, 120), readonly=True) self.add_setting('zernike_base', 'enum', - self._get_zernike_base, + lambda: self.zernike_information.Base, self._set_zernike_base, lambda: ZERNIKE_BASES.values()) - self.add_setting('zernike_polynomials', 'int', - self._get_zernike_polynomials, - self._set_zernike_polynomials, + self.add_setting('nr_zernike_polynomials', 'int', + lambda: self.zernike_information.Polynomials, + self._set_nr_zernike_polynomials, (0, 254)) self.add_setting('image_row_size', 'int', lambda: self.zernike_parameters.ImageRowSize, @@ -329,21 +337,9 @@ def __init__(self, self.buffer_size, readonly=True) - # Software buffers and parameters for data conversion. - self.num_buffers = 32 - self.add_setting('num_buffers', 'int', - lambda: self.num_buffers, - lambda val: self.set_num_buffers(val), - (1, 100)) - self.buffers = queue.Queue() - self.filled_buffers = queue.Queue() - self._buffer_size = None - self._buffers_valid = False - self._exposure_callback = None - # @property # def _acquiring(self): - # return self._camera_acquiring.get_value() + # return self._camera_acquiring.get_value() # TODO: Verify if we need this # # @keep_acquiring # def _enable_callback(self, use=False): @@ -353,98 +349,125 @@ def __init__(self, # def _acquiring(self, value): # pass # TODO: Verify if we need this - def set_num_buffers(self, num): - self.num_buffers = num - self._buffers_valid = False + def get_id(self): + self.get_setting('camera_sn') - def _purge_buffers(self): - """Purge buffers on both camera and PC.""" - self._logger.debug("Purging buffers.") - self._buffers_valid = False - if self._acquiring: - raise Exception('Can not modify buffers while camera acquiring.') - # TODO: implement the flush - while True: - try: - self.buffers.get(block=False) - except queue.Empty: - break - - def _create_buffers(self, num=None): - """Create buffers and store values needed to remove padding later.""" - if self._buffers_valid: - return - if num is None: - num = self.num_buffers - self._purge_buffers() - self._logger.debug("Creating %d buffers." % num) - intensity_map_size = self._intensity_map_size.get_value() - phase_map_size = self._phase_map_size.get_value() - zernike_indexes_size = self._zernike_indexes_size.get_value() - for i in range(num): - intensity_buf = np.require(np.empty(intensity_map_size), - dtype='uint32', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'OWNDATA']) - phase_buf = np.require(np.empty(phase_map_size), - dtype='float32', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'OWNDATA']) - # Create data instances in either way - self.tilt_information = self.ffi.new('TiltInfo *') - self.projection_coefficients_in = self.ffi.new("double[]", zernike_indexes_size) - tilts_buf = np.require(np.empty(2), - dtype='float32', + # def invalidate_settings(self, func): + # """Wrap functions that invalidate settings so settings are reloaded.""" + # outerself = self + # def wrapper(self, *args, **kwargs): + # func(self, *args, **kwargs) + # outerself._buffers_valid = False + # return wrapper + + def _fetch_data(self, timeout=5, debug=False): + """Fetch data.""" + try: + raw_data = self._grab_data() + except: + if debug: + self._logger.debug('There is no data to grab') + return None + + return raw_data + + def _grab_data(self): + """Uses the SDK's GrabLiveMode to get the phase and intensity maps and + calls the Zernike functions to calculate the projection of the polynomials""" + # TODO: This is wrong it should be a multiplication but the SDK returns a wrong settup of the array size + phase_map_width = intensity_map_width = self.get_setting('phase_size_width') + phase_map_height = intensity_map_height = self.get_setting('phase_size_height') + nr_zernike_polynomials = self.get_setting('nr_zernike_polynomials') + # phase_map = self.ffi.new("double[]", phase_map_size) + phase_map = np.require(np.empty((phase_map_width, phase_map_height)), + dtype='float64', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'WRITEABLE', + 'OWNDATA']) + # intensity_map = self.ffi.new("double[]", intensity_map_size) + intensity_map = np.require(np.empty((intensity_map_width, intensity_map_height)), + dtype='float64', requirements=['C_CONTIGUOUS', 'ALIGNED', + 'WRITEABLE', 'OWNDATA']) - rms_buf = np.require(np.empty(1), - dtype='float32', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'OWNDATA']) - ptv_buf = np.require(np.empty(1), - dtype='int32', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'OWNDATA']) - zernike_buf = np.require(np.empty(zernike_indexes_size), - dtype='float64', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'OWNDATA']) - - self.buffers.put([intensity_buf, - phase_buf, - tilts_buf, - rms_buf, - ptv_buf, - zernike_buf]) - self._buffers_valid = True - - def invalidate_buffers(self, func): - """Wrap functions that invalidate buffers so buffers are recreated.""" - outerself = self - def wrapper(self, *args, **kwargs): - func(self, *args, **kwargs) - outerself._buffers_valid = False - return wrapper - - def _fetch_data(self, timeout=5, debug=False): - """Fetch data and recycle buffers.""" + tilt = self.ffi.new('TiltInfo *') + # projection_coefficients = self.ffi.new("double[]", nr_zernike_polynomials) + projection_coefficients = np.require(np.empty(nr_zernike_polynomials), + dtype='float64', + requirements=['C_CONTIGUOUS', + 'ALIGNED', + 'WRITEABLE', + 'OWNDATA']) + + phase_pt = self.ffi.cast('double *', phase_map.ctypes.data) + phase_size = self.ffi.sizeof(phase_pt) + intensity_pt = self.ffi.cast('double *', intensity_map.ctypes.data) + intensity_size = self.ffi.sizeof(intensity_pt) + zernike_polynomials = self.ffi.cast('double *', projection_coefficients.ctypes.data) + zernike_polynomials_size = self.ffi.sizeof(zernike_polynomials) try: - raw = self.filled_buffers.get(timeout=timeout) + self.SID4_SDK.GrabLiveMode(self.session_id, + phase_pt, + phase_size, + intensity_pt, + intensity_size, + tilt, + self.analysis_array_size, + self.error_code) + print('error after GrabLiveMode') + print(self.error_code[0]) except: - raise Exception('There is no data in the buffer') - data = [np.copy(x) for x in raw] - - # Requeue the buffer. TODO: we should check if buffer size has not been changed elsewhere. - self.buffers.put(raw) + print(self.error_code[0]) + Exception('Could not GrabLiveMode') - return data + try: + self.zernike_SDK.Zernike_PhaseProjection(phase_map, + phase_size, + self.analysis_array_size, + zernike_polynomials, + zernike_polynomials_size, + self.error_code) + except: + Exception('Could not do PhaseProjection') + + def _process_data(self, data): + """Apply necessary transformations to data to be served to the client. + + Return as a dictionary: + - intensity_map: a 2D array containing the intensity + - linearity: some measure of the linearity of the data. + Simple saturation at the intensity map might not be enough to indicate + if we are exposing correctly to get a accurate measure of the phase. + - phase_map: a 2D array containing the phase + - tilts: a tuple containing X and Y tilts + - RMS: the root mean square measurement + - PtoV: peak to valley measurement + - zernike_polynomials: a list with the relevant Zernike polynomials + """ + # input data contains [intensity_map, phase_map, tilt, projection_coefficients] + processed_data = {'intensity_map': self._apply_transform(data[0]), + 'phase_map': self._apply_transform(data[1]), + 'tilts': data[2], + 'zernike_polynomials': data[3], + 'RMS': data[1].std(), + 'PtoV': data[3].ptp()} + + return processed_data + + def _apply_transform(self, array): + """Apply self._transform to a numpy array""" + flips = (self._transform[0], self._transform[1]) + rot = self._transform[2] + + # Choose appropriate transform based on (flips, rot). + return {(0, 0): np.rot90(array, rot), + (0, 1): np.flipud(np.rot90(array, rot)), + (1, 0): np.fliplr(np.rot90(array, rot)), + (1, 1): np.fliplr(np.flipud(np.rot90(array, rot))) + }[flips] def abort(self): """Abort acquisition.""" @@ -459,83 +482,20 @@ def initialize(self): from the input profile""" self._logger.debug('Opening SDK...') try: - self._set_user_profile_file(self.user_profile_file) + self.SID4_SDK.OpenSID4(self.user_profile_file, self.session_id, self.error_code) + print(self.error_code[0]) + self._refresh_user_profile_params() except: raise Exception('SDK could not open.') - # get the camera attributes and populate them - # self._get_camera_attribute_list() - - # Default setup - # self.trigger_mode.set_string('Software')z - # self._cycle_mode.set_string('Continuous') - - # def callback(*args): - # data = self._fetch_data(timeout=500) - # timestamp = time.time() - # if data is not None: - # self._dispatch_buffer.put((data, timestamp)) - # return 0 - # else: - # return -1 - # - # self._exposure_callback = SDK3.CALLBACKTYPE(callback) - - # def _get_camera_attribute_list(self): - # """This method updates the camera attributes stored in the camera_attributes dict - # - # To call at camera initialization""" - # self.nr_of_attributes = self.ffi.new('long *') - # print(self.nr_of_attributes[0]) - # # try: - # # self.SID4_SDK.Camera_GetNumberOfAttribute(self.session_id, self.nr_of_attributes, self.error_code) - # # except: - # # raise Exception('Could not get nr_of_attributes.') - # print(self.error_code[0]) - # print(self.nr_of_attributes[0]) - # - # self.attribute_id = self.ffi.new('unsigned short int[]', self.nr_of_attributes[0]) - # self.attribute_id_bs = self.ffi.cast('long', self.nr_of_attributes[0]) - # self.attributes = self.ffi.new('char[]', self.buffer_size) - # self.attributes_bs = self.ffi.cast('long', self.buffer_size) - # self.attribute_min = self.ffi.new('long[]', self.nr_of_attributes[0]) - # self.attribute_min_bs = self.ffi.cast('long', self.nr_of_attributes[0] * 4) - # self.attribute_max = self.ffi.new('long[]', self.nr_of_attributes[0]) - # self.attribute_max_bs = self.ffi.cast('long', self.nr_of_attributes[0] * 4) - # # self.attribute_id = self.ffi.new('unsigned short int[]', 9) - # # self.attribute_id_bs = self.ffi.cast('long', 36) - # # self.attributes = self.ffi.new('char[]', 1024) - # # self.attributes_bs = self.ffi.cast('long', 1024) - # # self.attribute_min = self.ffi.new('long[]', 9) - # # self.attribute_min_bs = self.ffi.cast('long', 36) - # # self.attribute_max = self.ffi.new('long[]', 9) - # # self.attribute_max_bs = self.ffi.cast('long', 36) - # - # self.SID4_SDK.Camera_GetAttributeList(self.session_id, - # self.attribute_id, - # self.attribute_id_bs, - # self.attributes, - # self.attributes_bs, - # self.attribute_min, - # self.attribute_min_bs, - # self.attribute_max, - # self.attribute_max_bs, - # self.error_code) - # - # print(self.error_code[0]) - # - # self.attributes_list = self.ffi.string(self.attributes) # [:-2].split(sep = b'\t') - # - # for i in range(self.nr_of_attributes[0]): - # self.camera_attributes[self.attributes_list[i]] = (self.attribute_id[i], - # self.attribute_min[i], - # self.attribute_max[i]) + # Load zernike analysis settings + try: + self._set_zernike_attributes() + except: + Exception('Could not load zernike attributes') - def _on_enable(self): - self._logger.debug('Enabling SID4.') - if self._acquiring: - self._acquisition_stop() - self._create_buffers() + # update the settings that are not implemented through the user profile or the camera attributes + self.update_settings(settings={'nr_zernike_polynomials': 5}) self._logger.debug('Initializing SID4...') try: @@ -543,6 +503,16 @@ def _on_enable(self): except: raise Exception('SID4 could not Init. Error code: ', self.error_code[0]) + def _on_enable(self): + self._logger.debug('Enabling SID4.') + if self._acquiring: + self._acquisition_stop() + self._logger.debug('Starting SID4 acquisition...') + + self._acquisition_start() + self._logger.debug('Acquisition enabled: %s' % self._acquiring) + return True + def _acquisition_start(self): try: self.SID4_SDK.CameraStart(self.session_id, self.error_code) @@ -561,14 +531,12 @@ def _acquisition_stop(self): def _on_disable(self): self.abort() - self._buffers_valid = False try: self.SID4_SDK.CameraClose(self.session_id, self.error_code) except: raise Exception('Unable to close camera. Error code: ', str(self.error_code[0])) def _on_shutdown(self): - self.abort() self.disable() try: self.SID4_SDK.CloseSID4(self.session_id, self.error_code) @@ -639,12 +607,10 @@ def _on_shutdown(self): ##a = phase @keep_acquiring - def _set_user_profile_file(self, path=None): + def _refresh_user_profile_params(self): """Sets the user profile file but also reloads the SDK with OpenSID4 and repopulates the settings with GetUserProfile""" - self.user_profile_file = path try: - self.SID4_SDK.OpenSID4(self.user_profile_file, self.session_id, self.error_code) self.SID4_SDK.GetUserProfile(self.session_id, self.user_profile_name, self.user_profile_name_bs, @@ -664,43 +630,26 @@ def _set_user_profile_file(self, path=None): self.camera_sn_bs, self.analysis_array_size, self.error_code) + except: raise Exception('SDK could not open. Error code: ', self.error_code[0]) - print(self.session_id[0]) - print(self.ffi.string(self.user_profile_name)) - print(int(self.user_profile_name_bs)) - print(self.user_profile_file) - print(int(self.user_profile_file_bs)) - print(self.ffi.string(self.user_profile_description)) - print(int(self.user_profile_description_bs)) - print(self.ffi.string(self.user_profile_last_reference)) - print(int(self.user_profile_last_reference_bs)) - print(self.ffi.string(self.user_profile_directory)) - print(int(self.user_profile_directory_bs)) - print(self.ffi.string(self.sdk_version)) - print(int(self.sdk_version_bs)) - print(self.analysis_information.GratingPositionMm) - print(self.analysis_information.wavelengthNm) - print(self.analysis_information.RemoveBackgroundImage) - print(self.analysis_information.PhaseSize_width) - print(self.analysis_information.PhaseSize_Height) - print(self.camera_information.FrameRate) - print(self.camera_information.TriggerMode) - print(self.camera_information.Gain) - print(self.camera_information.ExposureTime) - print(self.camera_information.PixelSizeM) - print(self.camera_information.NumberOfCameraRecorded) - print(self.ffi.string(self.camera_sn)) - print(int(self.camera_sn_bs)) - print(self.analysis_array_size.nRow) - print(self.analysis_array_size.nCol) - print(self.error_code[0]) + def _get_camera_attribute(self, attribute): + attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) + value = self.ffi.new('double *') + try: + self.SID4_SDK.Camera_GetAttribute(self.session_id, + attribute_id, + value,self.error_code) + except: + raise Exception('Could not get camera attribute: %s', attribute) + + return value[0] @keep_acquiring def _set_camera_attribute(self, attribute, value): attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) - new_value = self.ffi.cast('double', value) + new_value = self.ffi.new('double *', value) try: self.SID4_SDK.Camera_SetAttribute(self.session_id, attribute_id, @@ -709,8 +658,19 @@ def _set_camera_attribute(self, attribute, value): except: raise Exception('Could not change camera attribute: %s', attribute) - def _modify_user_profile(self, save=False): + def _set_zernike_attributes(self, ): + self.zernike_array_size = self.ffi.new("ArraySize *", [20,20]) + self.zernike_max_pol = self.ffi.cast("unsigned char", 5) + self.zernike_SDK.Zernike_GetZernikeInfo(self.zernike_information, + self.zernike_array_size, + self.zernike_version, + self.zernike_version_bs) + + self.zernike_SDK.Zernike_UpdateProjection_fromUserProfile(self.user_profile_directory, + self.zernike_max_pol, + self.error_code) + def _modify_user_profile(self, save=False): try: self.SID4_SDK.ModifyUserProfile(self.session_id, self.analysis_information, @@ -731,6 +691,9 @@ def _save_user_profile(self): except: Exception('Could not save user profile') + def _get_frame_rate(self): + return self._get_camera_attribute(attribute='FrameRate') + def _set_frame_rate(self, rate): self._set_camera_attribute('FrameRate', rate) if self.error_code[0]: @@ -774,40 +737,44 @@ def _set_remove_background_image(self, remove, save=False): self._modify_user_profile(save=save) - def _set_phase_size_width(self, width, save=False): - self.analysis_information.PhaseSize_width = width - self._modify_user_profile(save=save) + def _get_phase_size_width(self): + # HACK: This is actually not the right way to collect this info but cffi is + # otherwise getting different bytes from memory + return int.from_bytes(self.ffi.buffer(self.analysis_information)[17:21], 'little') - def _set_phase_size_height(self, height, save=False): - self.analysis_information.PhaseSize_Height = height - self._modify_user_profile(save=save) + # def _set_phase_size_width(self, width, save=False): + # self.analysis_information.PhaseSize_width = width + # self._modify_user_profile(save=save) - def _get_intensity_size_width(self): - pass + def _get_phase_size_height(self): + # HACK: This is actually not the right way to collect this info but cffi is + # otherwise getting different bytes from memory + return int.from_bytes(self.ffi.buffer(self.analysis_information)[21:25], 'little') - def _set_intensity_size_width(self): - pass - - def _get_intensity_size_height(self): - pass - - def _set_intensity_size_height(self): - pass - - def _get_zernike_base(self): - pass + # def _set_phase_size_height(self, height, save=False): + # self.analysis_information.PhaseSize_Height = height + # self._modify_user_profile(save=save) def _set_zernike_base(self): pass - def _get_zernike_polynomials(self): - pass - - def _set_zernike_polynomials(self): - pass + def _set_nr_zernike_polynomials(self, polynomials): + self.zernike_information.Polynomials = polynomials + # Zernike_UpdateProjection_fromUserProfile(char + # UserProfileDirectory[], unsigned + # char + # PolynomialOrder, long * ErrorCode); + # self.zernike_SDK.Zernike_UpdateProjection_fromParameter if __name__ == '__main__': wfs = SID4Device() wfs.initialize() + wfs.enable() + print(wfs.get_setting('frame_rate')) + wfs.set_setting('frame_rate', 3) + print(wfs.get_setting('frame_rate')) + data = wfs._fetch_data() + wfs.disable() + wfs.shutdown() diff --git a/microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp b/microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp index 260ceb90..8ea397df 100644 --- a/microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp +++ b/microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp @@ -1,5 +1,5 @@ /* - © Copyright 2008 by PHASICS. + � Copyright 2008 by PHASICS. All rights reserved. @File: SID4_SDK_FileAnalysis_Example.cpp @@ -20,6 +20,12 @@ "CloseSID4" closes the SID4 session. It releases memory devoted to the session. */ +/*compile like +cd C:\Users\omxt\PycharmProjects\microscope\microscope\wavefront_sensors +cl -I "C:\Users\omxt\PycharmProjects\microscope\microscope\wavefront_sensors\DLL SDK\Headers" SID4_SDK_FileAnalysis_Example.cpp /link "C:\Users\omxt\PycharmProjects\microscope\microscope\wavefront_sensors\DLL SDK\LIB\SID4_SDK.lib" + +*/ + #include #include #include @@ -30,10 +36,10 @@ const int bufSize=1024; void main(void) { - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; + char userProfile_File[]="C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt"; + char inteferogram_File[]="C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Interferograms\\Interferogram.tif"; SDK_Reference SessionID=0; - int nrow, ncol; + long nrow, ncol; long Error=0; char UserProfile_Name[bufSize]=""; long uspName_bufSize = bufSize; @@ -74,14 +80,18 @@ void main(void) nrow = AnalysisInformation.PhaseSize_Height; ncol = AnalysisInformation.PhaseSize_width; + printf ("AnalysisInfo-H=%d\n",nrow); + printf ("AnalysisInfo-W=%d\n",ncol); + printf ("pixelsizeM=%g\n",CameraInformation.PixelSizeM); + //// memory allocation for Phase and Intensity before calling FileAnalysis TiltInfo TiltInformation; long Intensity_bufSize = nrow*ncol; long Phase_bufSize = nrow*ncol; ArraySize AnalysisArraySize; - AnalysisArraySize.height=nrow; - AnalysisArraySize.width=ncol; + AnalysisArraySize.nRow=nrow; + AnalysisArraySize.nCol=ncol; float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); diff --git a/microscope/wavefront_sensors/SID4_SDK_cffi.py b/microscope/wavefront_sensors/SID4_SDK_cffi.py index dd4f0e51..b5ddc09c 100644 --- a/microscope/wavefront_sensors/SID4_SDK_cffi.py +++ b/microscope/wavefront_sensors/SID4_SDK_cffi.py @@ -24,11 +24,9 @@ from cffi import FFI -# We import the headers from a file in order to avoid copyright issues -wfs_profile_path = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' + header_defs = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" header_path = "C:\\Program Files (x86)\\SID4_SDK\\DLL SDK\\Headers\\SID4_SDK.h" -dll_path = "SID4_SDK.dll" cdef_from_file = '' try: @@ -49,19 +47,31 @@ ffi.cdef(cdef_from_file) -SDK = ffi.dlopen(dll_path) +SDK = ffi.dlopen("SID4_SDK.dll") +ZernikeSDK = ffi.dlopen("Zernike_SDK.dll") buffer_size = 1024 -nrow = 0 -ncol = 0 +user_profile_file_in = ffi.new("char[]", buffer_size) +user_profile_file_in_bs = ffi.cast("long", buffer_size) +user_profile_file_in = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' + +interferogram_file = ffi.new("char[]", buffer_size) +interferogram_file_bs = ffi.cast("long", buffer_size) +interferogram_file = "C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Interferograms\\Interferogram.tif" + +session_id = ffi.new('SDK_Reference *', 0) + +nrow = ffi.new("int *", 0) +ncol = ffi.new("int *", 0) + +error_code = ffi.new('long *', 0) user_profile_name = ffi.new("char[]", buffer_size) # initialize first with a certain buffer size user_profile_name_bs = ffi.cast("long", buffer_size) -user_profile_file = ffi.new("char[]", buffer_size) -user_profile_file_bs = ffi.cast("long", buffer_size) -user_profile_file = wfs_profile_path +user_profile_file_out = ffi.new("char[]", buffer_size) +user_profile_file_out_bs = ffi.cast("long", buffer_size) user_profile_description = ffi.new("char[]", buffer_size) user_profile_description_bs = ffi.cast("long", buffer_size) @@ -82,34 +92,23 @@ camera_sn = ffi.new("char[]", buffer_size) camera_sn_bs = ffi.cast("long", buffer_size) -session_id = ffi.new('SDK_Reference *') -error_code = ffi.new('long *', 0) +image_size = ffi.new("ArraySize *") -tilt_information = ffi.new('TiltInfo *') -analysis_array_size = ffi.new('ArraySize *', [64,64]) -camera_array_size = ffi.new('ArraySize *') +# tilt_information = ffi.new('TiltInfo *') +# analysis_array_size = ffi.new('ArraySize *', [64,64]) print('Opening SDK...') -SDK.OpenSID4(user_profile_file, session_id, error_code) +SDK.OpenSID4(user_profile_file_in, session_id, error_code) +print('Session ID:') +print(session_id) +print('Error code:') print(error_code[0]) -print('Initializing WFS...') -SDK.CameraInit(session_id, error_code) -print(error_code[0]) - -##print('Starting WFS...') -##SDK.CameraStart(session_id, error_code) -##print(error_code[0]) -## -##print('Stoping WFS...') -##SDK.CameraStop(session_id, error_code) -##print(error_code[0]) - SDK.GetUserProfile(session_id, user_profile_name, user_profile_name_bs, - user_profile_file, - user_profile_file_bs, + user_profile_file_out, + user_profile_file_out_bs, user_profile_description, user_profile_description_bs, user_profile_last_reference, @@ -122,17 +121,32 @@ camera_information, camera_sn, camera_sn_bs, - camera_array_size, + image_size, error_code) +print('Initializing WFS...') +SDK.CameraInit(session_id, error_code) +print(error_code[0]) + +##print('Starting WFS...') +##SDK.CameraStart(session_id, error_code) +##print(error_code[0]) +## +##print('Stoping WFS...') +##SDK.CameraStop(session_id, error_code) +##print(error_code[0]) + + ffi.string(user_profile_name) int(user_profile_name_bs) print('Grating Position', analysis_information.GratingPositionMm) print('wavelengthNm', analysis_information.wavelengthNm) print('RemoveBackgroundImage', analysis_information.RemoveBackgroundImage) -print('PhaseSize_width', analysis_information.PhaseSize_width) -print('PhaseSize_Height', analysis_information.PhaseSize_Height) +print('Analysis information PhaseSize_Height:') +print(int.from_bytes(ffi.buffer(analysis_information)[21:25], 'little')) +print('Analysis information PhaseSize_width:') +print(int.from_bytes(ffi.buffer(analysis_information)[17:21], 'little')) print('FrameRate', camera_information.FrameRate) print('TriggerMode', camera_information.TriggerMode) @@ -178,8 +192,6 @@ print('Max values:') print(ffi.unpack(attribute_max, nr_of_attributes[0])) - - phase = ffi.new('float[]', 4096) phase_bs = ffi.cast('long', 16384) intensity = ffi.new('float[]', 4096) From 77c91767fe947f90241be9323a87f18135fe728d Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Thu, 23 Nov 2017 20:32:21 +0100 Subject: [PATCH 05/27] Analysis attributes retturned correctly from buffers --- microscope/wavefront_sensors/SID4_SDK.py | 265 +++++++++--------- microscope/wavefront_sensors/SID4_SDK_cffi.py | 154 ++++++++-- 2 files changed, 262 insertions(+), 157 deletions(-) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index e04c4707..71cf664b 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -173,6 +173,7 @@ def __init__(self, self.camera_sn_bs = self.ffi.cast("long", self.buffer_size) self.camera_array_size = self.ffi.new("ArraySize *") + self.interferogram_array_size = self.ffi.new("ArraySize *") self.analysis_array_size = self.ffi.new("ArraySize *") # Create zernike-related parameters @@ -185,6 +186,11 @@ def __init__(self, self.polynomials_list = self.ffi.new("char[]", self.buffer_size) self.polynomials_list_bs = self.ffi.cast("long", self.buffer_size) + self.zernike_array_size = self.ffi.new("ArraySize *") + self.zernike_orders = self.ffi.cast("unsigned char", 0) + + self.acquisition_buffer = {} + # Call super __init__. super(SID4Device, self).__init__(**kwargs) @@ -239,11 +245,11 @@ def __init__(self, lambda: TRIGGER_MODES.keys()) self.add_setting('gain', 'int', lambda: self.camera_information.Gain, - self._set_gain, + self._set_gain, # TODO: not working lambda: (40, 210)) self.add_setting('exposure_time', 'enum', lambda: self.camera_information.ExposureTime, - self._set_exposure_time, + self._set_exposure_time, # TODO: Not working lambda: EXPOSURE_TIMES.keys()) self.add_setting('exposure_time_ms', 'float', lambda: EXPOSURE_TIMES[self.camera_information.ExposureTime][1], @@ -255,12 +261,12 @@ def __init__(self, None, lambda: (0.0, 0.1), readonly=True) - self.add_setting('camera_number_rows', 'int', + self.add_setting('camera_number_rows', 'int', # TODO: Not initialized lambda: self.camera_array_size.nRow, None, lambda: (0, 480), readonly=True) - self.add_setting('camera_number_cols', 'int', + self.add_setting('camera_number_cols', 'int', # TODO: Not initialized lambda: self.camera_array_size.nCol, None, lambda: (0, 640), @@ -273,11 +279,11 @@ def __init__(self, # Add analysis settings self.add_setting('reference_source', 'enum', - lambda: int(self.reference_source), + lambda: int(self.reference_source), # TODO: verify returns 0 self._set_reference_source, lambda: REFERENCE_SOURCES.keys()) self.add_setting('reference_path', 'str', - lambda: int(self.reference_path), + lambda: int(self.reference_path), # TODO: error self._set_reference_path, self.buffer_size) self.add_setting('grating_position_mm', 'float', @@ -305,34 +311,29 @@ def __init__(self, readonly=True) self.add_setting('zernike_base', 'enum', lambda: self.zernike_information.Base, - self._set_zernike_base, + self._set_zernike_base, # TODO: not working lambda: ZERNIKE_BASES.values()) - self.add_setting('nr_zernike_polynomials', 'int', - lambda: self.zernike_information.Polynomials, - self._set_nr_zernike_polynomials, + self.add_setting('nr_zernike_orders', 'int', + self._get_nr_zernike_orders, + self._set_nr_zernike_orders, (0, 254)) - self.add_setting('image_row_size', 'int', - lambda: self.zernike_parameters.ImageRowSize, + self.add_setting('nr_zernike_polynomials', 'int', + self._get_nr_zernike_polynomials, None, - (0, 480), + (0, 36000), readonly=True) - self.add_setting('image_col_size', 'int', - lambda: self.zernike_parameters.ImageColSize, + self.add_setting('zernike_mask_col_size', 'int', + lambda: self.zernike_parameters.MaskColSize, None, - (0, 640), + (0, 160), readonly=True) - self.add_setting('mask_row_size', 'int', + self.add_setting('zernike_mask_row_size', 'int', lambda: self.zernike_parameters.MaskRowSize, None, - (0, 480), - readonly=True) - self.add_setting('mask_col_size', 'int', - lambda: self.zernike_parameters.MaskColSize, - None, - (0, 640), + (0, 120), readonly=True) self.add_setting('zernike_version', 'str', - lambda: self.zernike_version, + lambda: self.zernike_version, # TODO: error None, self.buffer_size, readonly=True) @@ -352,86 +353,90 @@ def __init__(self, def get_id(self): self.get_setting('camera_sn') + def invalidate_settings(self, func): + """Wrap functions that invalidate settings so settings are reloaded.""" + outerself = self + def wrapper(self, *args, **kwargs): + func(self, *args, **kwargs) + outerself._settings_valid = False + return wrapper - # def invalidate_settings(self, func): - # """Wrap functions that invalidate settings so settings are reloaded.""" - # outerself = self - # def wrapper(self, *args, **kwargs): - # func(self, *args, **kwargs) - # outerself._buffers_valid = False - # return wrapper + def _create_buffers(self): + """Creates a buffer to store the data. It also reloads all necessary parameters""" + self._refresh_zernike_attributes() - def _fetch_data(self, timeout=5, debug=False): - """Fetch data.""" - try: - raw_data = self._grab_data() - except: - if debug: - self._logger.debug('There is no data to grab') - return None - - return raw_data + self.analysis_array_size.nCol = self.get_setting('phase_size_width') + self.analysis_array_size.nRow = self.get_setting('phase_size_height') - def _grab_data(self): - """Uses the SDK's GrabLiveMode to get the phase and intensity maps and - calls the Zernike functions to calculate the projection of the polynomials""" - # TODO: This is wrong it should be a multiplication but the SDK returns a wrong settup of the array size + # TODO: split phase and intensity map sizes. They are not necessarily the same phase_map_width = intensity_map_width = self.get_setting('phase_size_width') phase_map_height = intensity_map_height = self.get_setting('phase_size_height') nr_zernike_polynomials = self.get_setting('nr_zernike_polynomials') - # phase_map = self.ffi.new("double[]", phase_map_size) - phase_map = np.require(np.empty((phase_map_width, phase_map_height)), - dtype='float64', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'WRITEABLE', - 'OWNDATA']) - # intensity_map = self.ffi.new("double[]", intensity_map_size) - intensity_map = np.require(np.empty((intensity_map_width, intensity_map_height)), - dtype='float64', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'WRITEABLE', - 'OWNDATA']) + phase_map_size = intensity_map_size = phase_map_width * phase_map_height + + phase_map = self.ffi.new("float[]", phase_map_size) + phase_map_bs = self.ffi.cast("long", phase_map_size) + np_phase_map = np.frombuffer(buffer=self.ffi.buffer(phase_map), + dtype='float32') + np_phase_map.shape = (phase_map_width, phase_map_height) + + intensity_map = self.ffi.new("float[]", intensity_map_size) + intensity_map_bs = self.ffi.cast("long", intensity_map_size) + np_intensity_map = np.frombuffer(buffer=self.ffi.buffer(intensity_map), + dtype='float32') + np_intensity_map.shape = (intensity_map_width, intensity_map_height) + tilt = self.ffi.new('TiltInfo *') - # projection_coefficients = self.ffi.new("double[]", nr_zernike_polynomials) - projection_coefficients = np.require(np.empty(nr_zernike_polynomials), - dtype='float64', - requirements=['C_CONTIGUOUS', - 'ALIGNED', - 'WRITEABLE', - 'OWNDATA']) - - phase_pt = self.ffi.cast('double *', phase_map.ctypes.data) - phase_size = self.ffi.sizeof(phase_pt) - intensity_pt = self.ffi.cast('double *', intensity_map.ctypes.data) - intensity_size = self.ffi.sizeof(intensity_pt) - zernike_polynomials = self.ffi.cast('double *', projection_coefficients.ctypes.data) - zernike_polynomials_size = self.ffi.sizeof(zernike_polynomials) + np_tilt = np.frombuffer(buffer=self.ffi.buffer(tilt), + dtype='float32') + + projection_coefficients = self.ffi.new("double[]", nr_zernike_polynomials) + projection_coefficients_bs = self.ffi.cast("long", nr_zernike_polynomials) + np_projection_coefficients = np.frombuffer(buffer=self.ffi.buffer(projection_coefficients), + dtype='float64') + + self.acquisition_buffer.update({'phase_map': phase_map, + 'phase_map_bs': phase_map_bs, + 'np_phase_map': np_phase_map, + 'intensity_map': intensity_map, + 'intensity_map_bs': intensity_map_bs, + 'np_intensity_map': np_intensity_map, + 'tilts': tilt, + 'np_tilts': np_tilt, + 'zernike_polynomials': projection_coefficients, + 'zernike_polynomials_bs': projection_coefficients_bs, + 'np_zernike_polynomials': np_projection_coefficients}) + + def _fetch_data(self, timeout=5, debug=False): + """Uses the SDK's GrabLiveMode to get the phase and intensity maps and + calls the Zernike functions to calculate the projection of the polynomials""" try: self.SID4_SDK.GrabLiveMode(self.session_id, - phase_pt, - phase_size, - intensity_pt, - intensity_size, - tilt, + self.acquisition_buffer['phase_map'], + self.acquisition_buffer['phase_map_bs'], + self.acquisition_buffer['intensity_map'], + self.acquisition_buffer['intensity_map_bs'], + self.acquisition_buffer['tilts'], self.analysis_array_size, self.error_code) - print('error after GrabLiveMode') - print(self.error_code[0]) + self._logger.debug('Grabbed image...') except: print(self.error_code[0]) Exception('Could not GrabLiveMode') - try: - self.zernike_SDK.Zernike_PhaseProjection(phase_map, - phase_size, - self.analysis_array_size, - zernike_polynomials, - zernike_polynomials_size, + self.zernike_SDK.Zernike_PhaseProjection(self.acquisition_buffer['phase_map'], + self.acquisition_buffer['phase_map_bs'], + self.zernike_array_size, + self.acquisition_buffer['zernike_polynomials'], + self.acquisition_buffer['zernike_polynomials_bs'], self.error_code) except: - Exception('Could not do PhaseProjection') + Exception('Could not get PhaseProjection') + + return {'phase_map': np.copy(self.acquisition_buffer['np_phase_map']), + 'intensity_map': np.copy(self.acquisition_buffer['np_intensity_map']), + 'tilts': np.copy(self.acquisition_buffer['np_tilts']), + 'zernike_polynomials': np.copy(self.acquisition_buffer['np_zernike_polynomials'])} def _process_data(self, data): """Apply necessary transformations to data to be served to the client. @@ -448,12 +453,12 @@ def _process_data(self, data): - zernike_polynomials: a list with the relevant Zernike polynomials """ # input data contains [intensity_map, phase_map, tilt, projection_coefficients] - processed_data = {'intensity_map': self._apply_transform(data[0]), - 'phase_map': self._apply_transform(data[1]), - 'tilts': data[2], - 'zernike_polynomials': data[3], - 'RMS': data[1].std(), - 'PtoV': data[3].ptp()} + processed_data = {'intensity_map': self._apply_transform(data['intensity_map']), + 'phase_map': self._apply_transform(data['phase_map']), + 'tilts': data['tilts'], + 'zernike_polynomials': data['zernike_polynomials'], + 'RMS': data['phase_map'].std(), + 'PtoV': data['phase_map'].ptp()} return processed_data @@ -483,19 +488,15 @@ def initialize(self): self._logger.debug('Opening SDK...') try: self.SID4_SDK.OpenSID4(self.user_profile_file, self.session_id, self.error_code) - print(self.error_code[0]) self._refresh_user_profile_params() except: raise Exception('SDK could not open.') - # Load zernike analysis settings - try: - self._set_zernike_attributes() - except: - Exception('Could not load zernike attributes') - # update the settings that are not implemented through the user profile or the camera attributes - self.update_settings(settings={'nr_zernike_polynomials': 5}) + self.update_settings(settings={'nr_zernike_orders': 5}) + + # Load zernike analysis settings + self._refresh_zernike_attributes() self._logger.debug('Initializing SID4...') try: @@ -509,6 +510,8 @@ def _on_enable(self): self._acquisition_stop() self._logger.debug('Starting SID4 acquisition...') + self._create_buffers() + self._acquisition_start() self._logger.debug('Acquisition enabled: %s' % self._acquiring) return True @@ -628,12 +631,34 @@ def _refresh_user_profile_params(self): self.camera_information, self.camera_sn, self.camera_sn_bs, - self.analysis_array_size, + self.interferogram_array_size, self.error_code) except: raise Exception('SDK could not open. Error code: ', self.error_code[0]) + def _refresh_zernike_attributes(self): + self.zernike_parameters.ImageRowSize = self.zernike_array_size.nCol = self.get_setting('phase_size_width') + self.zernike_parameters.ImageColSize = self.zernike_array_size.nRow = self.get_setting('phase_size_height') + # TODO: the two following are not necessarily true + self.zernike_parameters.MaskRowSize = self.get_setting('phase_size_width') + self.zernike_parameters.MaskColSize = self.get_setting('phase_size_height') + self.zernike_parameters.Base = self.get_setting('zernike_base') + + try: + self.zernike_SDK.Zernike_UpdateProjection_fromParameter(self.zernike_parameters, + self.zernike_orders, + self.error_code) + except: + Exception('Could not update zernike parameters') + try: + self.zernike_SDK.Zernike_GetZernikeInfo(self.zernike_information, + self.zernike_array_size, + self.zernike_version, + self.zernike_version_bs) + except: + Exception('Could not get zernike info') + def _get_camera_attribute(self, attribute): attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) value = self.ffi.new('double *') @@ -658,18 +683,6 @@ def _set_camera_attribute(self, attribute, value): except: raise Exception('Could not change camera attribute: %s', attribute) - def _set_zernike_attributes(self, ): - self.zernike_array_size = self.ffi.new("ArraySize *", [20,20]) - self.zernike_max_pol = self.ffi.cast("unsigned char", 5) - self.zernike_SDK.Zernike_GetZernikeInfo(self.zernike_information, - self.zernike_array_size, - self.zernike_version, - self.zernike_version_bs) - - self.zernike_SDK.Zernike_UpdateProjection_fromUserProfile(self.user_profile_directory, - self.zernike_max_pol, - self.error_code) - def _modify_user_profile(self, save=False): try: self.SID4_SDK.ModifyUserProfile(self.session_id, @@ -758,23 +771,23 @@ def _get_phase_size_height(self): def _set_zernike_base(self): pass - def _set_nr_zernike_polynomials(self, polynomials): - self.zernike_information.Polynomials = polynomials + def _get_nr_zernike_orders(self): + return self.zernike_orders + + def _set_nr_zernike_orders(self, orders): + self.zernike_orders = orders # Zernike_UpdateProjection_fromUserProfile(char # UserProfileDirectory[], unsigned # char # PolynomialOrder, long * ErrorCode); # self.zernike_SDK.Zernike_UpdateProjection_fromParameter + def _get_nr_zernike_polynomials(self): + return int.from_bytes(self.ffi.buffer(self.zernike_information)[2:6], 'little') -if __name__ == '__main__': + def _set_zernike_mask_row_size(self, row_size): + pass + + def _set_zernike_mask_col_size(self, col_size): + pass - wfs = SID4Device() - wfs.initialize() - wfs.enable() - print(wfs.get_setting('frame_rate')) - wfs.set_setting('frame_rate', 3) - print(wfs.get_setting('frame_rate')) - data = wfs._fetch_data() - wfs.disable() - wfs.shutdown() diff --git a/microscope/wavefront_sensors/SID4_SDK_cffi.py b/microscope/wavefront_sensors/SID4_SDK_cffi.py index b5ddc09c..35976612 100644 --- a/microscope/wavefront_sensors/SID4_SDK_cffi.py +++ b/microscope/wavefront_sensors/SID4_SDK_cffi.py @@ -62,9 +62,6 @@ session_id = ffi.new('SDK_Reference *', 0) -nrow = ffi.new("int *", 0) -ncol = ffi.new("int *", 0) - error_code = ffi.new('long *', 0) user_profile_name = ffi.new("char[]", buffer_size) # initialize first with a certain buffer size @@ -94,13 +91,13 @@ image_size = ffi.new("ArraySize *") -# tilt_information = ffi.new('TiltInfo *') -# analysis_array_size = ffi.new('ArraySize *', [64,64]) +tilt_information = ffi.new('TiltInfo *') +analysis_array_size = ffi.new('ArraySize *', [80,80]) print('Opening SDK...') SDK.OpenSID4(user_profile_file_in, session_id, error_code) print('Session ID:') -print(session_id) +print(session_id[0]) print('Error code:') print(error_code[0]) @@ -192,28 +189,126 @@ print('Max values:') print(ffi.unpack(attribute_max, nr_of_attributes[0])) -phase = ffi.new('float[]', 4096) -phase_bs = ffi.cast('long', 16384) -intensity = ffi.new('float[]', 4096) -intensity_bs = ffi.cast('long', 16384) +phase = ffi.new('float[]', 6400) +phase_bs = ffi.cast('long', 25600) +intensity = ffi.new('float[]', 6400) +intensity_bs = ffi.cast('long', 25600) image = ffi.new("short int[307200]") -image_bs = ffi.cast("long", 307200) +image_bs = ffi.cast("long", 614400) print('Starting Live mode...') SDK.StartLiveMode(session_id, error_code) print(error_code[0]) -print('Grabbing image...') -SDK.GrabImage(session_id, image, image_bs, camera_array_size, error_code) +# print('Grabbing image...') +# SDK.GrabImage(session_id, image, image_bs, camera_array_size, error_code) +# print(error_code[0]) +# +# print('Part of the image') +# print([x for x in image[0:20]]) + +print('Grabbing Live mode...') +SDK.GrabLiveMode(session_id, + phase, + phase_bs, + intensity, + intensity_bs, + tilt_information, + analysis_array_size, + error_code) print(error_code[0]) -print('Part of the image') -print([x for x in image[0:20]]) +print('creating zernike parameters') +zernike_parameters = ffi.new("ZernikeParam *") +zernike_information = ffi.new("ZernikeInformation *") +phase_map_array_size = ffi.new("ArraySize *") +zernike_version = ffi.new("char[]", buffer_size) +zernike_version_bs = ffi.cast("long", buffer_size) + +proj_coefficients = ffi.new("double[]", 253) +proj_coefficients_bs = ffi.cast("long", 2024) +max_nr_polynomials = ffi.cast("unsigned char", 21) + +print('Populating zernike parameters') +ZernikeSDK.Zernike_GetZernikeInfo(zernike_information, + phase_map_array_size, + zernike_version, + zernike_version_bs) + +print('Changing zernike parameters') +# # Using Zernike_UpdateProjection_fromUserProfile: updates analysis array size but not nr of polynomials +# ZernikeSDK.Zernike_UpdateProjection_fromUserProfile(user_profile_file_out, +# max_nr_polynomials, +# error_code) +# +# # Using Zernike_UpdateProjection_fromArray: Not in library!!! +# ZernikeSDK.Zernike_UpdateProjection_fromArray(phase, +# phase_bs, +# analysis_array_size, +# max_nr_polynomials, +# error_code) +# +# Using Zernike_UpdateProjection_fromParameter: updates analysis array size but not nr of polynomials +zernike_parameters.ImageRowSize = 80 +zernike_parameters.ImageColSize = 80 +zernike_parameters.MaskRowSize = 80 +zernike_parameters.MaskColSize = 80 +zernike_parameters.Base = 0 +ZernikeSDK.Zernike_UpdateProjection_fromParameter(zernike_parameters, + max_nr_polynomials, + error_code) + +# Using Zernike_UpdateProjection_fromParameters2 + +''' +/*! + * Zernike_UpdateProjection_fromParameters2 + * This function computes the Projection Basis (Zernike or Legendre) according + * the input parameters which are: + * - Dimension & Position of the analysis pupil + * - Dimension of the image that will contain the analysis pupil + * - Choice of the Basis that will be computed (Zernike or Legendre) + * + * If the input "Mask_Obstruction" array is not empty, the program will used + * it for computing the Projection Basis. In this case, the dimension of the + * "Mask_Obstruction" array should be identic to the image dimension specified + * in the ProjectionBasis_Parameters. + */ +void __cdecl Zernike_UpdateProjection_fromParameters2( + ZKLParam *ProjectionBasis_Parameters, float Mask_Obstruction[], + long mask_bufSize, ArraySize *MaskArraySize, unsigned char PolynomialsOrder, + long *ErrorCode); +''' +# +# # Using Zernike_UpdateProjection_fromPhaseFile +# phase_file = ffi.new("char[]", buffer_size) +# phase_file = b'C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Results\\PHA example.tif' +# +# ZernikeSDK.Zernike_UpdateProjection_fromPhaseFile(phase_file, +# max_nr_polynomials, +# error_code) +# +print(error_code[0]) -print('Grabbing Live mode...') -SDK.GrabLiveMode(session_id, phase, phase_bs, intensity, intensity_bs, tilt_information, analysis_array_size, error_code) +# zernike_information.Polynomials = 5 + +print('Getting parameters back') +ZernikeSDK.Zernike_GetZernikeInfo(zernike_information, + phase_map_array_size, + zernike_version, + zernike_version_bs) + +print('Getting polynomials...') +ZernikeSDK.Zernike_PhaseProjection(phase, + phase_bs, + analysis_array_size, + proj_coefficients, + proj_coefficients_bs, + error_code) +# print(error_code[0]) + ## ##print('Part of the phase') ##print([x for x in phase[0:20]]) @@ -221,17 +316,14 @@ ##print('Part of the intensity') ##print([x for x in intensity[0:20]]) ## -##print('Stopping Live mode...') -##SDK.StopLiveMode(session_id, error_code) -##print(error_code[0]) -## -##print('Closing camera...') -##SDK.CameraClose(session_id, error_code) -##print(error_code[0]) -## -##print('Closing SDK...') -##SDK.CloseSID4(session_id, error_code) -##print(error_code[0]) -## -### keep phase alive -##a = phase +print('Stopping Live mode...') +SDK.StopLiveMode(session_id, error_code) +print(error_code[0]) + +print('Closing camera...') +SDK.CameraClose(session_id, error_code) +print(error_code[0]) + +print('Closing SDK...') +SDK.CloseSID4(session_id, error_code) +print(error_code[0]) From b737810bb6dbd9e2760e44e19569d395868a8e09 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Thu, 23 Nov 2017 22:16:09 +0100 Subject: [PATCH 06/27] Implemented Software trigger --- microscope/wavefront_sensors/SID4_SDK.py | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 71cf664b..cdf9d53c 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -197,6 +197,10 @@ def __init__(self, # Create camera attributes. TODO: this should be created programmatically self.camera_attributes = camera_attributes + # Create a queue to trigger software acquisitions + self._trigger_queue = queue.Queue() + self._set_trigger_mode(0) + # Add profile settings self.add_setting('user_profile_name', 'str', lambda: self.ffi.string(self.user_profile_name), @@ -261,12 +265,12 @@ def __init__(self, None, lambda: (0.0, 0.1), readonly=True) - self.add_setting('camera_number_rows', 'int', # TODO: Not initialized + self.add_setting('camera_number_rows', 'int', # TODO: Not initialized lambda: self.camera_array_size.nRow, None, lambda: (0, 480), readonly=True) - self.add_setting('camera_number_cols', 'int', # TODO: Not initialized + self.add_setting('camera_number_cols', 'int', # TODO: Not initialized lambda: self.camera_array_size.nCol, None, lambda: (0, 640), @@ -361,6 +365,14 @@ def wrapper(self, *args, **kwargs): outerself._settings_valid = False return wrapper + @Pyro4.expose() + def soft_trigger(self): + if self._acquiring and self._is_software_trigger: + self._trigger_queue.put(1) + else: + raise Exception('cannot trigger if camera is not acquiring or is not in software trigger mode.') + + def _create_buffers(self): """Creates a buffer to store the data. It also reloads all necessary parameters""" self._refresh_zernike_attributes() @@ -410,6 +422,10 @@ def _create_buffers(self): def _fetch_data(self, timeout=5, debug=False): """Uses the SDK's GrabLiveMode to get the phase and intensity maps and calls the Zernike functions to calculate the projection of the polynomials""" + if self._is_software_trigger: # TODO: fix this with a proper interrupt + if self._trigger_queue.empty(): return None + self._trigger_queue.get() + try: self.SID4_SDK.GrabLiveMode(self.session_id, self.acquisition_buffer['phase_map'], @@ -452,7 +468,6 @@ def _process_data(self, data): - PtoV: peak to valley measurement - zernike_polynomials: a list with the relevant Zernike polynomials """ - # input data contains [intensity_map, phase_map, tilt, projection_coefficients] processed_data = {'intensity_map': self._apply_transform(data['intensity_map']), 'phase_map': self._apply_transform(data['phase_map']), 'tilts': data['tilts'], @@ -716,6 +731,10 @@ def _set_trigger_mode(self, mode): self._set_camera_attribute('Trigger', mode) if self.error_code[0]: self.camera_information.TriggerMode = mode + if mode == 0: + self._is_software_trigger = True + else: + self._is_software_trigger = False def _set_gain(self, gain): self._set_camera_attribute('Gain', gain) From b9c24545f411c31531f730d872fbd09105eed469 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Mon, 27 Nov 2017 18:56:47 +0100 Subject: [PATCH 07/27] Service working Fixed some getters and setters of camera parameters --- microscope/wavefront_sensors/SID4_SDK.py | 134 ++++++++++------------- 1 file changed, 56 insertions(+), 78 deletions(-) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index cdf9d53c..65853355 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -36,9 +36,9 @@ # Trigger mode to type. TRIGGER_MODES = { - 0: devices.TRIGGER_SOFT, # 0:'continuous mode' TODO: check all this - 1: devices.TRIGGER_SOFT, # 1:'mode0' - 2: devices.TRIGGER_BEFORE # 2:'mode1' + 0: devices.TRIGGER_SOFT, # 0:'continuous mode' + 1: devices.TRIGGER_BEFORE, # 1:'mode0' + 2: devices.TRIGGER_DURATION # 2:'mode1' } FRAME_RATES = { 0: '3.75hz', @@ -198,8 +198,8 @@ def __init__(self, self.camera_attributes = camera_attributes # Create a queue to trigger software acquisitions + self._is_software_trigger = True self._trigger_queue = queue.Queue() - self._set_trigger_mode(0) # Add profile settings self.add_setting('user_profile_name', 'str', @@ -249,11 +249,11 @@ def __init__(self, lambda: TRIGGER_MODES.keys()) self.add_setting('gain', 'int', lambda: self.camera_information.Gain, - self._set_gain, # TODO: not working + self._set_gain, lambda: (40, 210)) self.add_setting('exposure_time', 'enum', lambda: self.camera_information.ExposureTime, - self._set_exposure_time, # TODO: Not working + self._set_exposure_time, lambda: EXPOSURE_TIMES.keys()) self.add_setting('exposure_time_ms', 'float', lambda: EXPOSURE_TIMES[self.camera_information.ExposureTime][1], @@ -357,6 +357,12 @@ def __init__(self, def get_id(self): self.get_setting('camera_sn') + def get_error(self): + return self.error_code[0] + + def get_camera_session(self): + return self.session_id[0] + def invalidate_settings(self, func): """Wrap functions that invalidate settings so settings are reloaded.""" outerself = self @@ -372,7 +378,6 @@ def soft_trigger(self): else: raise Exception('cannot trigger if camera is not acquiring or is not in software trigger mode.') - def _create_buffers(self): """Creates a buffer to store the data. It also reloads all necessary parameters""" self._refresh_zernike_attributes() @@ -435,7 +440,6 @@ def _fetch_data(self, timeout=5, debug=False): self.acquisition_buffer['tilts'], self.analysis_array_size, self.error_code) - self._logger.debug('Grabbed image...') except: print(self.error_code[0]) Exception('Could not GrabLiveMode') @@ -518,6 +522,7 @@ def initialize(self): self.SID4_SDK.CameraInit(self.session_id, self.error_code) except: raise Exception('SID4 could not Init. Error code: ', self.error_code[0]) + self._logger.debug('...SID4 Initialized') def _on_enable(self): self._logger.debug('Enabling SID4.') @@ -549,81 +554,19 @@ def _acquisition_stop(self): def _on_disable(self): self.abort() + + def _on_shutdown(self): + self.disable() try: self.SID4_SDK.CameraClose(self.session_id, self.error_code) except: raise Exception('Unable to close camera. Error code: ', str(self.error_code[0])) - def _on_shutdown(self): - self.disable() try: self.SID4_SDK.CloseSID4(self.session_id, self.error_code) except: raise Exception('Unable to close SDK. Error code: ', str(self.error_code[0])) - # phase = ffi.new('float[]', 4096) - # phase_bs = ffi.cast('long', 16384) - # intensity = ffi.new('float[]', 4096) - # intensity_bs = ffi.cast('long', 16384) - # - # image = ffi.new("short int[307200]") - # image_bs = ffi.cast("long", 307200) - # - # - # - # ffi.string(self.user_profile_name) - # int(user_profile_name_bs) - - # print(analysis_information.GratingPositionMm) - # print(analysis_information.wavelengthNm) - # print(analysis_information.RemoveBackgroundImage) - # print(analysis_information.PhaseSize_width) - # print(analysis_information.PhaseSize_Height) - # - # print(camera_information.FrameRate) - # print(camera_information.TriggerMode) - # print(camera_information.Gain) - # print(camera_information.ExposureTime) - # print(camera_information.PixelSizeM) - # print(camera_information.NumberOfCameraRecorded) - # - # - # print('Starting Live mode...') - # SDK.StartLiveMode(self.session_id, self.error_code) - # print(self.error_code[0]) - # - # print('Grabbing image...') - # SDK.GrabImage(self.session_id, image, image_bs, camera_array_size, self.error_code) - # print(self.error_code[0]) - # - # print('Part of the image') - # print([x for x in image[0:20]]) - # - # print('Grabbing Live mode...') - # SDK.GrabLiveMode(self.session_id, phase, phase_bs, intensity, intensity_bs, self.tilt_information, self.analysis_array_size, self.error_code) - # print(self.error_code[0]) - ## - ##print('Part of the phase') - ##print([x for x in phase[0:20]]) - ## - ##print('Part of the intensity') - ##print([x for x in intensity[0:20]]) - ## - ##print('Stopping Live mode...') - ##SDK.StopLiveMode(session_id, error_code) - ##print(error_code[0]) - ## - ##print('Closing camera...') - ##SDK.CameraClose(session_id, error_code) - ##print(error_code[0]) - ## - ##print('Closing SDK...') - ##SDK.CloseSID4(session_id, error_code) - ##print(error_code[0]) - ## - ### keep phase alive - ##a = phase - @keep_acquiring def _refresh_user_profile_params(self): """Sets the user profile file but also reloads the SDK with OpenSID4 and @@ -680,7 +623,8 @@ def _get_camera_attribute(self, attribute): try: self.SID4_SDK.Camera_GetAttribute(self.session_id, attribute_id, - value,self.error_code) + value, + self.error_code) except: raise Exception('Could not get camera attribute: %s', attribute) @@ -724,12 +668,12 @@ def _get_frame_rate(self): def _set_frame_rate(self, rate): self._set_camera_attribute('FrameRate', rate) - if self.error_code[0]: + if not self.error_code[0]: self.camera_information.FrameRate = rate def _set_trigger_mode(self, mode): self._set_camera_attribute('Trigger', mode) - if self.error_code[0]: + if not self.error_code[0]: self.camera_information.TriggerMode = mode if mode == 0: self._is_software_trigger = True @@ -738,12 +682,14 @@ def _set_trigger_mode(self, mode): def _set_gain(self, gain): self._set_camera_attribute('Gain', gain) - if self.error_code[0]: + if not self.error_code[0]: self.camera_information.Gain = gain def _set_exposure_time(self, index): + # if type(index) == str: + # index = EXPOSURE_TIME_TO_INDEX['incex'] self._set_camera_attribute('Exposure', index) - if self.error_code[0]: + if not self.error_code[0]: self.camera_information.ExposureTime = index def _set_reference_source(self, source, save=False): @@ -810,3 +756,35 @@ def _set_zernike_mask_row_size(self, row_size): def _set_zernike_mask_col_size(self, col_size): pass +if __name__=='__main__': + + wfs = SID4Device() + wfs.initialize() + wfs.enable() + print('Current gain: ', wfs.get_setting('gain')) + print('Changing gain') + wfs.set_setting('gain', 190) + print('Current gain: ', wfs.get_setting('gain')) + exposure_time = wfs.get_setting('exposure_time') + print(exposure_time) + wfs.set_setting('exposure_time', 5) + # wfs.set_setting('exposure_time', '1/200s') + print('Changing Exposure time') + exposure_time = wfs.get_setting('exposure_time') + print(exposure_time) + frame_rate = wfs.get_setting('frame_rate') + print('FrameRate: ', frame_rate) + wfs.set_setting('frame_rate', 0.0) + frame_rate = wfs.get_setting('frame_rate') + print('FrameRate: ', frame_rate) + for i in range(10): + wfs.soft_trigger() + print(wfs._fetch_data()['tilts']) + wfs.set_setting('frame_rate', 4) + frame_rate = wfs.get_setting('frame_rate') + print('FrameRate: ', frame_rate) + for i in range(10): + wfs.soft_trigger() + print(wfs._fetch_data()['tilts']) + + wfs.shutdown() \ No newline at end of file From 7fbedc672821de4958860e821eb6c487f2248854 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Fri, 15 Dec 2017 08:22:52 +0100 Subject: [PATCH 08/27] Implemented mask and pupil --- microscope/wavefront_sensors/SID4_SDK.py | 208 ++++++++++++++++++++--- 1 file changed, 184 insertions(+), 24 deletions(-) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 65853355..7244a2c7 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -22,6 +22,11 @@ The interface with the SID4 SDK has been implemented using cffi API 'in line' """ +# TODO: implement timeout setting +# TODO: implement change mask/pupil +# TODO: Better error handling +# TODO: write UniTests + from microscope import devices from microscope.devices import WavefrontSensorDevice, keep_acquiring from cffi import FFI @@ -41,11 +46,11 @@ 2: devices.TRIGGER_DURATION # 2:'mode1' } FRAME_RATES = { - 0: '3.75hz', - 1: '7.5hz', - 2: '15hz', - 3: '30hz', - 4: '60hz' + 1: '3.75hz', + 2: '7.5hz', + 3: '15hz', + 4: '30hz', + 5: '60hz' } EXPOSURE_TIMES = { # Asa string and in msec 0: ('1/60s', 16.6667), @@ -82,13 +87,13 @@ # We setup some necessary configuration parameters. TODO: Move this into the config file # This is the default profile path of the camera WFS_PROFILE_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' +WFS_MASK_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\Imasque.msk' # We import the headers definitions used by cffi from a file in order to avoid copyright issues HEADER_DEFINITIONS = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" SID4_SDK_DLL_PATH = "SID4_SDK.dll" ZERNIKE_SDK_DLL_PATH = "Zernike_SDK.dll" - @Pyro4.expose # @Pyro4.behavior('single') class SID4Device(WavefrontSensorDevice): @@ -112,6 +117,7 @@ def __init__(self, sid4_sdk_dll_path=SID4_SDK_DLL_PATH, zernike_sdk_dll_path=ZERNIKE_SDK_DLL_PATH, wfs_profile_path=WFS_PROFILE_PATH, + wfs_mask_path=WFS_MASK_PATH, camera_attributes=CAMERA_ATTRIBUTES, **kwargs): self.header_definitions = header_definitions @@ -172,10 +178,26 @@ def __init__(self, self.camera_sn = self.ffi.new("char[]", self.buffer_size) self.camera_sn_bs = self.ffi.cast("long", self.buffer_size) - self.camera_array_size = self.ffi.new("ArraySize *") + # self.camera_array_size = self.ffi.new("ArraySize *") self.interferogram_array_size = self.ffi.new("ArraySize *") self.analysis_array_size = self.ffi.new("ArraySize *") + # Create ROI and pupil (mask) related parameters + self.user_mask_file = self.ffi.new("char[]", self.buffer_size) + self.user_mask_file_bs = self.ffi.cast("long", self.buffer_size) + self.user_mask_file = wfs_mask_path + + self.roi_global_rectangle = self.ffi.new("long []", 4) + self.roi_global_rectangle_bs = self.ffi.cast("long", 4) + + self.roi_nb_contours = self.ffi.new("unsigned short int *", 1) + + self.roi_contours_info = self.ffi.new("unsigned long []", 3) + self.roi_contours_info_bs = self.ffi.cast("long", 3) + + self.roi_contours_coordinates = self.ffi.new("long []", 4) + self.roi_contours_coordinates_bs = self.ffi.cast("long", 4) + # Create zernike-related parameters self.zernike_information = self.ffi.new("ZernikeInformation *") self.zernike_parameters = self.ffi.new("ZernikeParam *") @@ -197,6 +219,11 @@ def __init__(self, # Create camera attributes. TODO: this should be created programmatically self.camera_attributes = camera_attributes + # We compute a dict with exposure_times (in seconds) to Index. + self.exposure_time_s_to_index = {} + for key, exposure in EXPOSURE_TIMES.items(): + self.exposure_time_s_to_index[exposure[1]] = key + # Create a queue to trigger software acquisitions self._is_software_trigger = True self._trigger_queue = queue.Queue() @@ -265,13 +292,13 @@ def __init__(self, None, lambda: (0.0, 0.1), readonly=True) - self.add_setting('camera_number_rows', 'int', # TODO: Not initialized - lambda: self.camera_array_size.nRow, + self.add_setting('camera_number_rows', 'int', + lambda: self._get_camera_attribute('ImageHeight'), None, lambda: (0, 480), readonly=True) - self.add_setting('camera_number_cols', 'int', # TODO: Not initialized - lambda: self.camera_array_size.nCol, + self.add_setting('camera_number_cols', 'int', + lambda: self._get_camera_attribute('ImageWidth'), None, lambda: (0, 640), readonly=True) @@ -337,7 +364,7 @@ def __init__(self, (0, 120), readonly=True) self.add_setting('zernike_version', 'str', - lambda: self.zernike_version, # TODO: error + lambda: self.zernike_version, # TODO: error None, self.buffer_size, readonly=True) @@ -516,6 +543,11 @@ def initialize(self): # Load zernike analysis settings self._refresh_zernike_attributes() + self._logger.debug('Loading zernike analysis settings...') + + # Load mask descriptor file + self._load_pupil_descriptor() + self._logger.debug('Loading pupil settings...') self._logger.debug('Initializing SID4...') try: @@ -685,9 +717,19 @@ def _set_gain(self, gain): if not self.error_code[0]: self.camera_information.Gain = gain + def get_exposure_time(self): + return 1000 * self.get_setting('exposure_time_ms') + + def set_exposure_time(self, value): + """The SID4 does not accept arbitrary exposure times but a limited + number of values that are expressed in EXPOSURE_TIMES. + We here take the closest value and apply that exposure time""" + index = min(self.exposure_time_s_to_index, key=lambda x: abs(x - value)) + self._set_exposure_time(index=index) + def _set_exposure_time(self, index): - # if type(index) == str: - # index = EXPOSURE_TIME_TO_INDEX['incex'] + if type(index) == str: + index = EXPOSURE_TIME_TO_INDEX['index'] self._set_camera_attribute('Exposure', index) if not self.error_code[0]: self.camera_information.ExposureTime = index @@ -715,6 +757,104 @@ def _set_remove_background_image(self, remove, save=False): self._modify_user_profile(save=save) + def _get_sensor_shape(self): + return (self.get_setting('camera_number_cols'), + self.get_setting('camera_number_rows')) + + def _get_binning(self): + # TODO: Check if it is not better to put here the 'natural' binning that we get after interferogram analysis + return 1, 1 + + def _set_binning(self, h_bin, v_bin): + """binning is not implemented""" + return False + + def _get_roi(self): + roi = (self.roi_global_rectangle[0], + self.roi_global_rectangle[1], + self.roi_global_rectangle[2] - self.roi_global_rectangle[0], + self.roi_global_rectangle[3] - self.roi_global_rectangle[1]) + return roi + + def _set_roi(self, left, top, width, height): + right = left + width + bottom = top + height + self.roi_global_rectangle = [left, top, right, bottom] + return self.set_pupil() + + def _get_pupil(self): + return self._pupil_shape, self._pupil_edge + + def _set_pupil(self, shape, edge): + """Implementation of a simple version to set the pupil to a circle or rectangle + (shape) embedded into the main ROI with a certain margin (edge). It is possible + to generate more complex pupils but I don't currently see the need.""" + self.roi_nb_contours[0] = 1 # number of sub-ROIs defining the pupil + # sub-ROI characteristics, there are three value for each sub-ROI + self.roi_contours_info[0] = 1 # contour is the external (0) or internal edge (1) of an sub-ROI + self.roi_contours_info[1] = 4 # shape of the contour: 3 = Rectangle, 4 = Oval or Circle + self.roi_contours_info[2] = 4 # Nb of coordinates defining the contour. The minimum value is 4 in the case of a simple form (rectangle, circle, ellipse) and can be higher in the case of a polygon + self.roi_contours_info_bs = 3 * self.roi_nb_contours[0] + # Coordinates of the Rectangle of each sub-ROI the ones behind the others. + # They are not referred to the global ROI so we have to calculate them according to it + # Note that the global rectangle must be defined before setting the pupil + self.roi_contours_coordinates[0] = self.roi_global_rectangle[0] + edge + self.roi_contours_coordinates[1] = self.roi_global_rectangle[1] + edge + self.roi_contours_coordinates[2] = self.roi_global_rectangle[2] - edge + self.roi_contours_coordinates[3] = self.roi_global_rectangle[3] - edge + self.roi_contours_coordinates_bs = 4 * self.roi_nb_contours[0] + + return self._change_mask() + + @keep_acquiring + def _change_mask(self): + """Calls SDK ChangeMask using the current parameters. Checks for coherence + Return False if not successful""" + # TODO check coherence + mask_file = self.ffi.new("char[]", self.buffer_size) + mask_file = b'' + try: + self.SID4_SDK.ChangeMask(self.session_id, + mask_file, + self.roi_global_rectangle, + self.roi_global_rectangle_bs, + self.roi_nb_contours, + self.roi_contours_info, + self.roi_contours_info_bs, + self.roi_contours_coordinates, + self.roi_contours_coordinates_bs, + self.error_code) + except: + Exception('Could not change mask') + return False + + return True + + def _load_pupil_descriptor(self, mask_descriptor_path=None): + """Loads the mask parameters from a file""" + if mask_descriptor_path is None: + mask_descriptor_path = self.user_mask_file + + self.error_code[0] = 0 + self.SID4_SDK.LoadMaskDescriptor(self.session_id, + mask_descriptor_path, + self.roi_global_rectangle, + self.roi_global_rectangle_bs, + self.roi_nb_contours, + self.roi_contours_info, + self.roi_contours_info_bs, + self.roi_contours_coordinates, + self.roi_contours_coordinates_bs, + self.error_code) + # catch if the allocated arrays were not enough to contain all subROIs. + if self.error_code[0] == 7005: + self.roi_contours_info = self.ffi.new("unsigned long []", 3 * self.roi_nb_contours[0]) + self.roi_contours_info_bs = self.ffi.cast("long", 3 * self.roi_nb_contours[0]) + + self.roi_contours_coordinates = self.ffi.new("long []", 4 * self.roi_nb_contours[0]) + self.roi_contours_coordinates_bs = self.ffi.cast("long", 4 * self.roi_nb_contours[0]) + self._load_pupil_descriptor(mask_descriptor_path=mask_descriptor_path) + def _get_phase_size_width(self): # HACK: This is actually not the right way to collect this info but cffi is # otherwise getting different bytes from memory @@ -756,15 +896,33 @@ def _set_zernike_mask_row_size(self, row_size): def _set_zernike_mask_col_size(self, col_size): pass -if __name__=='__main__': +if __name__ == '__main__': wfs = SID4Device() wfs.initialize() wfs.enable() - print('Current gain: ', wfs.get_setting('gain')) - print('Changing gain') - wfs.set_setting('gain', 190) - print('Current gain: ', wfs.get_setting('gain')) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + print('Changing exposure_time') + wfs.set_setting('exposure_time', 0) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + for i in range(10): + wfs.soft_trigger() + print(wfs._fetch_data()['intensity_map'][30, 30]) + print('Changing exposure_time') + wfs.set_setting('exposure_time', 2) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + print('Changing exposure_time') + wfs.set_setting('exposure_time', 3) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + print('Changing exposure_time') + wfs.set_setting('exposure_time', 6) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + print('Changing exposure_time') + wfs.set_setting('exposure_time', 8) + print('Current exposure_time: ', wfs.get_setting('exposure_time')) + for i in range(10): + wfs.soft_trigger() + print(wfs._fetch_data()['intensity_map'][30, 30]) exposure_time = wfs.get_setting('exposure_time') print(exposure_time) wfs.set_setting('exposure_time', 5) @@ -774,17 +932,19 @@ def _set_zernike_mask_col_size(self, col_size): print(exposure_time) frame_rate = wfs.get_setting('frame_rate') print('FrameRate: ', frame_rate) - wfs.set_setting('frame_rate', 0.0) + wfs.set_setting('frame_rate', 1) frame_rate = wfs.get_setting('frame_rate') print('FrameRate: ', frame_rate) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['tilts']) - wfs.set_setting('frame_rate', 4) + print(wfs._fetch_data()['intensity_map'][30,30]) + wfs.set_setting('frame_rate', 5) frame_rate = wfs.get_setting('frame_rate') print('FrameRate: ', frame_rate) + print('Changing mask to 80') + wfs.set_roi(100, 100, 80, 80) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['tilts']) + print(wfs._fetch_data()['intensity_map'][30,30]) - wfs.shutdown() \ No newline at end of file + wfs.shutdown() From dc37b18117514c292643dfff489a9b0ff44afb8a Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Fri, 23 Feb 2018 18:51:03 +0100 Subject: [PATCH 09/27] Trigger is not working --- doc/SERVERS | 16 +++--- doc/example.py | 29 +++++----- microscope/devices.py | 37 ++++++++++--- microscope/wavefront_sensors/SID4_SDK.py | 67 +++++++++++------------- 4 files changed, 85 insertions(+), 64 deletions(-) diff --git a/doc/SERVERS b/doc/SERVERS index fc55a279..2018523e 100644 --- a/doc/SERVERS +++ b/doc/SERVERS @@ -7,13 +7,15 @@ Import device classes, then define entries in DEVICES as: # Function to create record for each device. from microscope.devices import device # Import device modules/classes here. -from microscope.testsuite.devices import TestCamera -from microscope.testsuite.devices import TestLaser -from microscope.testsuite.devices import TestFilterWheel +# from microscope.testsuite.devices import TestCamera +# from microscope.testsuite.devices import TestLaser +# from microscope.testsuite.devices import TestFilterWheel +from microscope.wavefront_sensors.SID4_SDK import SID4Device DEVICES = [ - device(TestCamera, '127.0.0.1', 8005, otherargs=1,), - device(TestLaser, '127.0.0.1', 8006), - device(TestFilterWheel, '127.0.0.1', 8007, - filters=[(0, 'GFP', 525), (1, 'RFP'), (2, 'Cy5')]), + device(SID4Device, '127.0.0.1', 8005) + # device(TestCamera, '127.0.0.1', 8005, otherargs=1,), + # device(TestLaser, '127.0.0.1', 8006), + # device(TestFilterWheel, '127.0.0.1', 8007, + # filters=[(0, 'GFP', 525), (1, 'RFP'), (2, 'Cy5')]), ] diff --git a/doc/example.py b/doc/example.py index a586dc9c..4dafe898 100644 --- a/doc/example.py +++ b/doc/example.py @@ -1,21 +1,22 @@ from microscope import clients -camera = clients.DataClient('PYRO:TestCamera@127.0.0.1:8005') -laser = clients.Client('PYRO:TestLaser@127.0.0.1:8006') +# camera = clients.DataClient('PYRO:TestCamera@127.0.0.1:8005') +# laser = clients.Client('PYRO:TestLaser@127.0.0.1:8006') +wfs = clients.DataClient('PYRO:SID4Device@127.0.0.1:8005') -laser.enable() -laser.set_power_mw(30) +# laser.enable() +# laser.set_power_mw(30) +# +# camera.enable() +# camera.set_exposure_time(0.15) -camera.enable() -camera.set_exposure_time(0.15) - -data = [] +wfs.enable() for i in range(10): - data.append(camera.trigger_and_wait()) - print("Frame %d captured." % i) - -print(data) + data = wfs.trigger_and_wait() + print(data[0]['tilts']) -laser.disable() -camera.disable() +# laser.disable() +# camera.disable() +wfs.disable() +wfs.shutdown() \ No newline at end of file diff --git a/microscope/devices.py b/microscope/devices.py index a602b277..1f14bfa4 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -786,10 +786,22 @@ def __init__(self, *args, **kwargs): self._zernike_phase_filter = [] # WaveLength at which the phase map is calculated self._wavelength_nm = None + self._pupil_shape = 4 # Defaults to circular shape + self._pupil_edge = 0 # Defaults to no edge self.add_setting('wavelength_nm', 'float', self._get_wavelength_nm, self._set_wavelength_nm, (300.0, 2000,0)) + self.add_setting('pupil_shape', 'int', + lambda: self._pupil_shape, + None, + (3, 4), + readonly=True) + self.add_setting('pupil_edge', 'int', + lambda: self._pupil_edge, + None, + (0, 100), + readonly=True) # Some acquisition related methods # @@ -798,11 +810,11 @@ def _process_data(self, data): """Apply necessary transformations to data to be served to the client. Return as a dictionary: + - phase_map: a 2D array containing the phase - intensity_map: a 2D array containing the intensity - linearity: some measure of the linearity of the data. Simple saturation at the intensity map might not be enough to indicate if we are exposing correctly to get a accurate measure of the phase. - - phase_map: a 2D array containing the phase - tilts: a tuple containing X and Y tilts - RMS: the root mean square measurement - PtoV: peak to valley measurement @@ -844,21 +856,34 @@ def get_pupil(self): return pupil @abc.abstractmethod - def _set_pupil(self): - """Set the pupil on the hardware, return True if successful.""" + def _set_pupil(self, shape=None, edge=None): + """Set the pupil on the hardware, return True if successful. + If type and edge are not specified, they should not change and follow the main ROI.""" return False @Pyro4.expose - def set_pupil(self, pupil): - """Set the pupil according to the provided parameters. + def set_pupil(self, shape=None, edge=None): + """Set the pupil referenced to the main ROI. + :param shape: this is defining the shape of the pupil. + 3 = Rectangle, 4 = Oval or Circle + :param edge: distance in pixels from the edge of the main ROI Return True if pupil set correctly, False otherwise.""" # TODO: depending on the format of the pupil, we might have to transform it # if self._transform[2]: # roi = (top, left, height, width) # else: # roi = (left, top, width, height) - return self._set_pupil(pupil) + if not edge: + edge = self._pupil_edge + else: + self._pupil_edge = edge + if not shape: + shape = self._pupil_shape + else: + self._pupil_shape = shape + + return self._set_pupil(shape=shape, edge=edge) @abc.abstractmethod def _set_reference(self, source): diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 7244a2c7..981825ba 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -23,7 +23,6 @@ The interface with the SID4 SDK has been implemented using cffi API 'in line' """ # TODO: implement timeout setting -# TODO: implement change mask/pupil # TODO: Better error handling # TODO: write UniTests @@ -314,7 +313,7 @@ def __init__(self, self._set_reference_source, lambda: REFERENCE_SOURCES.keys()) self.add_setting('reference_path', 'str', - lambda: int(self.reference_path), # TODO: error + lambda: self.ffi.string(self.reference_path), # TODO: verify self._set_reference_path, self.buffer_size) self.add_setting('grating_position_mm', 'float', @@ -364,23 +363,11 @@ def __init__(self, (0, 120), readonly=True) self.add_setting('zernike_version', 'str', - lambda: self.zernike_version, # TODO: error + lambda: self.ffi.string(self.zernike_version), # TODO: error None, self.buffer_size, readonly=True) - # @property - # def _acquiring(self): - # return self._camera_acquiring.get_value() # TODO: Verify if we need this - # - # @keep_acquiring - # def _enable_callback(self, use=False): - # pass # TODO: Verify if we need this - # - # @_acquiring.setter - # def _acquiring(self, value): - # pass # TODO: Verify if we need this - def get_id(self): self.get_setting('camera_sn') @@ -455,7 +442,8 @@ def _fetch_data(self, timeout=5, debug=False): """Uses the SDK's GrabLiveMode to get the phase and intensity maps and calls the Zernike functions to calculate the projection of the polynomials""" if self._is_software_trigger: # TODO: fix this with a proper interrupt - if self._trigger_queue.empty(): return None + if self._trigger_queue.empty(): + return None self._trigger_queue.get() try: @@ -480,6 +468,9 @@ def _fetch_data(self, timeout=5, debug=False): except: Exception('Could not get PhaseProjection') + if self._is_software_trigger: + self._trigger_queue.task_done() + return {'phase_map': np.copy(self.acquisition_buffer['np_phase_map']), 'intensity_map': np.copy(self.acquisition_buffer['np_intensity_map']), 'tilts': np.copy(self.acquisition_buffer['np_tilts']), @@ -628,12 +619,13 @@ def _refresh_user_profile_params(self): raise Exception('SDK could not open. Error code: ', self.error_code[0]) def _refresh_zernike_attributes(self): + """Applies changes to the zernike parameters. + :returns True if sucessful and False otherwise""" self.zernike_parameters.ImageRowSize = self.zernike_array_size.nCol = self.get_setting('phase_size_width') self.zernike_parameters.ImageColSize = self.zernike_array_size.nRow = self.get_setting('phase_size_height') # TODO: the two following are not necessarily true self.zernike_parameters.MaskRowSize = self.get_setting('phase_size_width') self.zernike_parameters.MaskColSize = self.get_setting('phase_size_height') - self.zernike_parameters.Base = self.get_setting('zernike_base') try: self.zernike_SDK.Zernike_UpdateProjection_fromParameter(self.zernike_parameters, @@ -641,6 +633,7 @@ def _refresh_zernike_attributes(self): self.error_code) except: Exception('Could not update zernike parameters') + return False try: self.zernike_SDK.Zernike_GetZernikeInfo(self.zernike_information, self.zernike_array_size, @@ -648,6 +641,9 @@ def _refresh_zernike_attributes(self): self.zernike_version_bs) except: Exception('Could not get zernike info') + return False + + return True def _get_camera_attribute(self, attribute): attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) @@ -729,7 +725,7 @@ def set_exposure_time(self, value): def _set_exposure_time(self, index): if type(index) == str: - index = EXPOSURE_TIME_TO_INDEX['index'] + index = self.exposure_time_s_to_index['index'] self._set_camera_attribute('Exposure', index) if not self.error_code[0]: self.camera_information.ExposureTime = index @@ -860,32 +856,29 @@ def _get_phase_size_width(self): # otherwise getting different bytes from memory return int.from_bytes(self.ffi.buffer(self.analysis_information)[17:21], 'little') - # def _set_phase_size_width(self, width, save=False): - # self.analysis_information.PhaseSize_width = width - # self._modify_user_profile(save=save) - def _get_phase_size_height(self): # HACK: This is actually not the right way to collect this info but cffi is # otherwise getting different bytes from memory return int.from_bytes(self.ffi.buffer(self.analysis_information)[21:25], 'little') - # def _set_phase_size_height(self, height, save=False): - # self.analysis_information.PhaseSize_Height = height - # self._modify_user_profile(save=save) - - def _set_zernike_base(self): - pass + def _set_zernike_base(self, base): + current_base = self.zernike_parameters.Base + self.zernike_parameters.Base = base + if self._refresh_zernike_attributes(): + return + else: + self.zernike_parameters.Base = current_base def _get_nr_zernike_orders(self): return self.zernike_orders def _set_nr_zernike_orders(self, orders): + current_orders = self.zernike_orders self.zernike_orders = orders - # Zernike_UpdateProjection_fromUserProfile(char - # UserProfileDirectory[], unsigned - # char - # PolynomialOrder, long * ErrorCode); - # self.zernike_SDK.Zernike_UpdateProjection_fromParameter + if self._refresh_zernike_attributes(): + return + else: + self.zernike_orders = current_orders def _get_nr_zernike_polynomials(self): return int.from_bytes(self.ffi.buffer(self.zernike_information)[2:6], 'little') @@ -907,7 +900,7 @@ def _set_zernike_mask_col_size(self, col_size): print('Current exposure_time: ', wfs.get_setting('exposure_time')) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][30, 30]) + print(wfs._fetch_data()['intensity_map'][15, 15]) print('Changing exposure_time') wfs.set_setting('exposure_time', 2) print('Current exposure_time: ', wfs.get_setting('exposure_time')) @@ -922,7 +915,7 @@ def _set_zernike_mask_col_size(self, col_size): print('Current exposure_time: ', wfs.get_setting('exposure_time')) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][30, 30]) + print(wfs._fetch_data()['intensity_map'][15, 15]) exposure_time = wfs.get_setting('exposure_time') print(exposure_time) wfs.set_setting('exposure_time', 5) @@ -937,7 +930,7 @@ def _set_zernike_mask_col_size(self, col_size): print('FrameRate: ', frame_rate) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][30,30]) + print(wfs._fetch_data()['intensity_map'][15,15]) wfs.set_setting('frame_rate', 5) frame_rate = wfs.get_setting('frame_rate') print('FrameRate: ', frame_rate) @@ -945,6 +938,6 @@ def _set_zernike_mask_col_size(self, col_size): wfs.set_roi(100, 100, 80, 80) for i in range(10): wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][30,30]) + print(wfs._fetch_data()['intensity_map'][15,15]) wfs.shutdown() From 2efc53db2be81a18367f426aa65399d185d38613 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Thu, 8 Mar 2018 14:23:06 +0100 Subject: [PATCH 10/27] Pre-SoftTrigger removal --- microscope/wavefront_sensors/ConsoleTests.py | 63 -- .../SID4_SDK_ChangeMask_Example.cpp | 0 .../SID4_SDK_ChangeMask_Example2.cpp | 0 .../SID4_SDK_FileAnalysis_Example.cpp | 0 .../SID4_SDK_GrabImage_Example.cpp | 0 .../SID4_SDK_OpenSID4_Example.cpp | 0 .../SID4_SDK_SaveMeasurement_Example.cpp | 0 microscope/wavefront_sensors/camera.py | 577 ------------------ microscope/wavefront_sensors/memoryHandler.py | 185 ------ 9 files changed, 825 deletions(-) delete mode 100644 microscope/wavefront_sensors/ConsoleTests.py rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_ChangeMask_Example.cpp (100%) rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_ChangeMask_Example2.cpp (100%) rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_FileAnalysis_Example.cpp (100%) rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_GrabImage_Example.cpp (100%) rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_OpenSID4_Example.cpp (100%) rename microscope/wavefront_sensors/{ => Examples}/SID4_SDK_SaveMeasurement_Example.cpp (100%) delete mode 100644 microscope/wavefront_sensors/camera.py delete mode 100644 microscope/wavefront_sensors/memoryHandler.py diff --git a/microscope/wavefront_sensors/ConsoleTests.py b/microscope/wavefront_sensors/ConsoleTests.py deleted file mode 100644 index 38ddcc68..00000000 --- a/microscope/wavefront_sensors/ConsoleTests.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import ctypes -UserProfileFile = 'C:\Program Files (x86)\SID4\phasics\SID4-079b default profile.txt' -SDK = ctypes.CDLL('SID4_SDK') -UserProfileLocation = ctypes.c_char_p(UserProfileFile) -class SDK_Reference(ctypes.Structure): - _fields_ = [('SDK_Reference', ctypes.c_int)] - -SessionID = SDK_Reference() -SessionIDRef = ctypes.byref(SessionID) -ErrorCode = ctypes.c_long() -ErrorCodeRef = ctypes.byref(ErrorCode) - -Phase = ctypes.c_float() -PhaseRef = ctypes.byref(Phase) - - -print('Opening SDK...') -SDK.OpenSID4(UserProfileLocation, SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - -print('Session is:') -print(SessionID.SDK_Reference) - -# print('Initializing camera...') -# SDK.CameraInit(SessionIDRef, ErrorCodeRef) -# -# print(ErrorCode) -# -# print('Starting Camera...') -# SDK.CameraStart(SessionIDRef, ErrorCodeRef) -# -# print(ErrorCode) - -print('Starting Camera LifeMode...') -SDK.StartLiveMode(SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - -####### - -####### -print('Stopping Camera LifeMode...') -SDK.StopLiveMode(SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - -print('Stopping Camera...') -SDK.CameraStop(SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - -print('Closing Camera...') -SDK.CameraClose(SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - -print('Closing SDK...') -SDK.CloseSID4(SessionIDRef, ErrorCodeRef) - -print(ErrorCode) - diff --git a/microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp diff --git a/microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example2.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example2.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_ChangeMask_Example2.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example2.cpp diff --git a/microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_FileAnalysis_Example.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_FileAnalysis_Example.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_FileAnalysis_Example.cpp diff --git a/microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_GrabImage_Example.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp diff --git a/microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_OpenSID4_Example.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp diff --git a/microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp similarity index 100% rename from microscope/wavefront_sensors/SID4_SDK_SaveMeasurement_Example.cpp rename to microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp diff --git a/microscope/wavefront_sensors/camera.py b/microscope/wavefront_sensors/camera.py deleted file mode 100644 index 6d837642..00000000 --- a/microscope/wavefront_sensors/camera.py +++ /dev/null @@ -1,577 +0,0 @@ -# This is Eric Branlund's code for the Zyla camera - -import memhandler -import neo - -import numpy -import numpy.ctypeslib -import Pyro4 - -import ctypes -import gc -import Queue -import threading -import time -import traceback - -## Needed to keep the daemon from only listening to requests originating -# from the local host. -MY_IP_ADDRESS = '10.0.0.2' - -## Cropping modes -(CROP_FULL, CROP_HALF, CROP_512, CROP_256, CROP_128) = range(5) - -## Trigger modes -(TRIGGER_INTERNAL, TRIGGER_EXTERNAL, TRIGGER_EXTERNAL_EXPOSURE) = range(3) - -STATIC_BLACK = numpy.zeros((512, 512), dtype = numpy.uint16) - -## Save an array as an image. Copied from -# http://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image -# Mostly this just makes it easier to view images for debugging. The image -# uses false color and thus isn't really useful for actual work. -def imsave(filename, array, vmin=None, vmax=None, cmap=None, - format=None, origin=None): - from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - from matplotlib.figure import Figure - - fig = Figure(figsize=array.shape[::-1], dpi=1, frameon=False) - canvas = FigureCanvas(fig) - fig.figimage(array, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin) - fig.savefig(filename, dpi=1, format=format) - - -## Because all functions in the neo module a) accept a camera handle as a -# first argument, and b) return an error code as the primary return value, -# we make this wrapper around the entire library to make interacting with -# it cleaner. It initializes the library, connects to the camera, and -# wraps every API function to handle error conditions. -# We also handle a couple of other functions through the memhandler.dll -# library via ctypes. They behave broadly similarly. -class WrappedAndor: - def __init__(self): - self.errorCodes = dict() - for key, value in neo.__dict__.iteritems(): - if callable(value): - self.__dict__[key] = self.wrapFunction(value) - # Also capture the error codes at this time so we can - # provide their names instead of bare numbers. - elif 'AT_ERR' == key[:6] or 'ERR_' == key[:4]: - self.errorCodes[value] = key - - ## Loaded object for memhandler.dll. - self.memLib = self.initMemhandler() - - startTime = time.time() - print "Initializing Andor library...", - error = neo.AT_InitialiseLibrary() - if error: - raise RuntimeException("Failed to initialize Andor library: %s" % self.errorCodes[error]) - print "done in %.2f seconds" % (time.time() - startTime) - error, numDevices = neo.AT_GetInt(neo.AT_HANDLE_SYSTEM, "DeviceCount") - if error: - raise RuntimeError("Failed to get number of devices: %s" % self.errorCodes[error]) - print "There are %d connected devices" % numDevices - error, self.handle = neo.AT_Open(0) - if error: - raise RuntimeError("Failed to connect to camera: %s" % self.errorCodes[error]) - elif self.handle == -1: - raise RuntimeError("Got an invalid handle from the camera") - else: - print "Connected to camera with handle",self.handle - - - ## Clean up after ourselves. - def __del__(self): - print "Close:",neo.AT_Close(self.handle) - print "Finalize:",neo.AT_FinaliseLibrary() - - - ## For the high-throughput functions AT_QueueBuffer and AT_WaitBuffer, we use - # ctypes instead of SWIG, in an attempt to avoid weird throughput issues we - # have otherwise. - def initMemhandler(self): - memLib = ctypes.CDLL('memhandler.dll') - # Args are handle, numBuffers, numElements - memLib.allocMemory.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int) - # Returns an error code. - memLib.allocMemory.restype = ctypes.c_int - # Construct the return type that's a point to a buffer of memory. - bufType = numpy.ctypeslib.ndpointer(dtype = numpy.uint16, ndim = 1, - flags = ('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE')) - # Args are output buffer, timeout - memLib.getImage.argtypes = (bufType, ctypes.c_int, ctypes.c_double) - # Returns an error code. - memLib.getImage.restype = ctypes.c_int - return memLib - - - ## Manual wrapper around the memhandler.allocMemory() function. - def allocMemory(self, numBuffers, numElements): - return self.processReturn(self.memLib.allocMemory( - self.handle, numBuffers, numElements)) - - - ## Manual wrapper around the memhandler.getImage() function. - def getImage(self, numElements, timeout): - imageBuffer = numpy.ndarray(numElements, dtype = numpy.uint16, - order = 'C') - # Multiply by 2 because there's 2 bytes per uint16. - error = self.memLib.getImage(imageBuffer, numElements * 2, .5) - if error: - raise RuntimeError("getImage failed") - return imageBuffer - - - ## Manual decorator function -- call the passed-in function with our - # handle, and raise an exception if an error occurs. - def wrapFunction(self, func): - def wrappedFunction(*args, **kwargs): - result = func(self.handle, *args, **kwargs) - return self.processReturn(result) - return wrappedFunction - - - ## Handle function return values. - # result may be a single value, a length-2 list, or a - # length-3+ list. We return None, the second value, or - # a tuple in those respective cases. - def processReturn(self, result): - errorCode = result - returnVal = None - if type(result) in [tuple, list]: # Type paranoia - errorCode = result[0] - if len(result) == 2: - returnVal = result[1] - else: - returnVal = tuple(result[1:]) - if errorCode: - errorString = "unknown error %s" % errorCode - if errorCode in self.errorCodes: - errorString = "error %s" % self.errorCodes[errorCode] - raise RuntimeError("An %s occurred." % errorString) - return returnVal - - - -wrappedAndor = WrappedAndor() - - - - -## Decorator function to put the camera in a mode where it can be -# interacted with. Mostly needed to stop acquisitions. -def resetCam(func): - def wrappedFunc(*args, **kwargs): - if wrappedAndor.AT_GetBool('CameraAcquiring'): - wrappedAndor.AT_Command('AcquisitionStop') - wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') - wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') - wrappedAndor.AT_SetInt('FrameCount', 1) - func(*args, **kwargs) - return wrappedFunc - - -## This class exposes various Andor library functions to outside clients, -# and handles collecting and transferring data. -class Camera: - def __init__(self): - ## Cached copy of the sensor width, since we need to access this - # regularly. - self.width = wrappedAndor.AT_GetInt('SensorWidth') - ## See self.width. - self.height = wrappedAndor.AT_GetInt('SensorHeight') - ## Current crop mode (e.g. CROP_FULL, CROP_512, etc.) - self.curCropMode = CROP_FULL - - print "Firmware version:",wrappedAndor.AT_GetString('FirmwareVersion') - print "Camera serial number:",wrappedAndor.AT_GetString('SerialNumber') - print "CCD sensor shape:",self.width,self.height - print "Bit depth:",wrappedAndor.AT_GetEnumIndex('BitDepth') - print "Pixel encoding:",wrappedAndor.AT_GetEnumIndex('PixelEncoding') - print "Shutter mode:",wrappedAndor.AT_GetEnumIndex('ElectronicShutteringMode') - print "Fan speed:",wrappedAndor.AT_GetEnumIndex('FanSpeed') - print "Sensor cooling:",wrappedAndor.AT_GetBool('SensorCooling') - print "Temp status:",wrappedAndor.AT_GetEnumIndex('TemperatureStatus') - print "Sensor temperature",self.getSensorTemperature() - print "Baseline level:",wrappedAndor.AT_GetInt('BaselineLevel') - - wrappedAndor.AT_SetEnumString("FanSpeed", "Off") - wrappedAndor.AT_SetBool("SensorCooling", True) - print "Pre-amp gain options:" - for i in xrange(wrappedAndor.AT_GetEnumCount("SimplePreAmpGainControl")): - print "%d:" % i, wrappedAndor.AT_GetEnumStringByIndex("SimplePreAmpGainControl", i) - wrappedAndor.AT_SetEnumString("SimplePreAmpGainControl", "16-bit (low noise & high well capacity)") - wrappedAndor.AT_SetEnumString("PixelReadoutRate", "280 MHz") - - self.setExposureTime(.1) - - self.dataThread = DataThread(self, self.width, self.height) - - self.setCrop(CROP_512) - self.setShutterMode(True) - self.dataThread.start() - - - ## Get told who we should be sending image data to, and how we - # should mark ourselves when we do. - def receiveClient(self, uri): - print "Receiving new client",uri - if uri is None: - self.dataThread.setClient(None) - self.dataThread.shouldSendImages = False - else: - connection = Pyro4.Proxy(uri) - connection._pyroTimeout = 5 - self.dataThread.setClient(connection) - self.dataThread.shouldSendImages = True - - - ## Stop sending images to the client, even if we still - # receive them. - def goSilent(self): - print "Switching to quiet mode" - self.dataThread.shouldSendImages = False - - - ## Start sending new images to the client again. - def goLoud(self): - print "Switching to loud mode" - self.dataThread.shouldSendImages = True - - - ## Stop acquiring images. - def abort(self): - wrappedAndor.AT_Command("AcquisitionStop") - - - ## Retrieve the specified number of images. This may fail - # if we ask for more images than we have, so the caller - # should be prepared to catch exceptions. - def getImages(self, numImages): - result = [] - for i in xrange(numImages): - image = self.extractOneImage() - result.append(image) - return result - - - ## Discard the specified number of images from the queue, - # defaulting to all of them. - def discardImages(self, numImages = None): - count = 0 - while count != numImages: - try: - self.extractOneImage() - count += 1 - except Exception, e: - # Got an exception, probably because the - # queue is empty, so we're all done here. - return - - - ## Set the exposure time, in seconds. - @resetCam - def setExposureTime(self, seconds = .01): - print "Set exposure time to", seconds, "seconds" - wrappedAndor.AT_SetFloat('ExposureTime', seconds) - - - ## Simple getter. - def getExposureTime(self): - return wrappedAndor.AT_GetFloat('ExposureTime') - - - ## Set the cropping mode to one of a few presets. In version - # 2 of the SDK, these were the only valid crop modes; in - # version 3, we can set any crop mode we want, but these - # presets are still convenient. - def setCrop(self, mode): - self.curCropMode = mode - width = [2560, 1392, 540, 240, 144][mode] - left = [1, 601, 1033, 1177, 1225][mode] - height = [2160, 1040, 512, 256, 128][mode] - top = [1, 561, 825, 953, 1017][mode] - self.setCropArbitrary(left, top, width, height) - - - ## Get the current crop mode. - def getCropMode(self): - return self.curCropMode - - - ## Set our cropping to an arbitrary region of interest. - def setCropArbitrary(self, left, top, width, height): - wrappedAndor.AT_SetInt('AOIWidth', width) - wrappedAndor.AT_SetInt('AOILeft', left) - wrappedAndor.AT_SetInt('AOIHeight', height) - wrappedAndor.AT_SetInt('AOITop', top) - - self.width = width - self.height = height - self.dataThread.setImageDimensions(width, height) - - # Reset the memory used to transfer images from the camera. - # We allocate about 500MB of RAM to the image buffer. Allocating - # too much memory seems to cause slowdowns and crashes, oddly enough. - imageBytes = wrappedAndor.AT_GetInt('ImageSizeBytes') - numImages = (500 * 1024 * 1024) / imageBytes - print "Allocating",numImages,"images at",imageBytes,"bytes per image" - wrappedAndor.allocMemory(numImages, imageBytes) - - stride = wrappedAndor.AT_GetInt('AOIStride') - div = float(stride) / width - print "Got stride",stride,"compare",width,"giving div",div - return (stride, width, int(div) == div) - - - ## Set an offset correction image to use. - def setOffsetCorrection(self, image): - self.dataThread.setOffsetCorrection(image) - - - ## Return true if a correction file is loaded - # for the current image dimensions. - def getIsOffsetCorrectionOn(self): - correction = self.dataThread.getOffsetCorrection() - return correction is not None and correction.shape == (self.height, self.width) - - - ## Retrieve the current sensor temperature in degrees Celsius. - def getSensorTemperature(self): - return wrappedAndor.AT_GetFloat('SensorTemperature') - - - ## Get the shape of the images we generate - def getImageShape(self): - return (self.width, self.height) - - - ## Set the trigger mode. - @resetCam - def setTrigger(self, triggerMode): - wrappedAndor.AT_SetEnumString('CycleMode', 'Continuous') - modeString = '' - if triggerMode == TRIGGER_INTERNAL: - wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') - modeString = 'internal trigger' - elif triggerMode == TRIGGER_EXTERNAL: - wrappedAndor.AT_SetEnumString('TriggerMode', 'External') - wrappedAndor.AT_Command('AcquisitionStart') - modeString = 'external trigger' - elif triggerMode == TRIGGER_EXTERNAL_EXPOSURE: - wrappedAndor.AT_SetEnumString('TriggerMode', 'External Exposure') - wrappedAndor.AT_Command('AcquisitionStart') - modeString = 'external exposure' - print "Set trigger mode to",modeString - - - ## Set the shutter mode. - def setShutterMode(self, isGlobal): - # 0 is rolling shutter; 1 is global. - wrappedAndor.AT_SetEnumIndex('ElectronicShutteringMode', int(isGlobal)) - - - ## Get the current shutter mode. - def getIsShutterModeGlobal(self): - return wrappedAndor.AT_GetEnumIndex('ElectronicShutteringMode') == 1 - - - ## Get the time needed to read out the image. - def getReadoutTime(self): - return wrappedAndor.AT_GetFloat('ReadoutTime') - - - ## Below this point lie debugging functions. - - - ## Acquire some number of images with internal trigger. - def triggerInternally(self, imageCount, exposureTime): - wrappedAndor.AT_Command('AcquisitionStop') - print "Acquiring %d images with exposure time %d" % (imageCount, exposureTime) - wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') - wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') - wrappedAndor.AT_SetInt('FrameCount', imageCount) - wrappedAndor.AT_SetFloat('ExposureTime', exposureTime / 1000.0) - # Hardcoded for now. - wrappedAndor.AT_SetFloat('FrameRate', 125) - wrappedAndor.AT_Command("AcquisitionStart") - - - ## Generate synthetic images at the specified framerate (in FPS). - def generateSequence(self, imageCount, frameRate): - threading.Thread(target = self.generateSequence2, args = [imageCount, frameRate]).start() - - - ## As above, but in a new thread. - def generateSequence2(self, imageCount, frameRate): - waitTime = 1 / float(frameRate) - image = numpy.zeros((self.width, self.height), dtype = numpy.uint16) - for i in xrange(imageCount): - curTime = time.clock() - self.dataThread.imageQueue.put((image, curTime)) - nextTime = time.clock() - putDelta = nextTime - curTime - if putDelta > waitTime * 1.25: - print "Putting is slow!",putDelta,waitTime - time.sleep(waitTime) - finalTime = time.clock() - if finalTime - nextTime > max(.02, waitTime * 1.25): - print "Sleeping is slow!",(finalTime - nextTime), waitTime - - - ## Set the gain mode as an index into the enums. Again, you generally - # shouldn't need to call this from outside. - def setGainEnum(self, val): - wrappedAndor.AT_SetEnumIndex('SimplePreAmpGainControl', val) - - - ## Set the readout rate as an index into the enums. Also for debugging. - def setReadoutEnum(self, val): - wrappedAndor.AT_SetEnumIndex('PixelReadoutRate', val) - - - ## Set the frame count to a different value and go into external - # trigger mode. Just for debugging. - def setFrameCount(self, val): - if wrappedAndor.AT_GetBool('CameraAcquiring'): - wrappedAndor.AT_Command('AcquisitionStop') - wrappedAndor.AT_SetEnumString('TriggerMode', 'Internal') - wrappedAndor.AT_SetEnumString('CycleMode', 'Fixed') - wrappedAndor.AT_SetInt('FrameCount', val) - wrappedAndor.AT_SetEnumString('TriggerMode', 'External') - wrappedAndor.AT_SetEnumString('CycleMode', 'Continuous') - wrappedAndor.AT_Command('AcquisitionStart') - - - -## This class retrieves images from the camera, and sends them to our -# client. -class DataThread(threading.Thread): - def __init__(self, parent, width, height): - threading.Thread.__init__(self) - - ## Loop back to parent to be able to communicate with it. - self.parent = parent - - ## Image dimensions, which we need for when we retrieve image - # data. Our parent is responsible for updating these for us - # via setImageDimensions(). - self.width = self.height = 0 - ## Lock on modifying the above. - self.sensorLock = threading.Lock() - - ## Connection to client - self.clientConnection = None - - ## Whether or not we should unload images from the camera - self.shouldSendImages = True - - ## Initial timestamp that we will use in conjunction with time.clock() - # to generate high-time-resolution timestamps. Just using time.time() - # straight-up on Windows only has accuracy of ~15ms. - self.initialTimestamp = time.time() + time.clock() - - ## Offset image array to subtract off of each image we - # receive. - self.offsetImage = None - - - ## Pull images from self.imageQueue and send them to the client. - def run(self): - count = 0 - gTime = None - getTime = 0 - fixTime = 0 - sendTime = 0 - while True: - # This will block indefinitely until images are available. - with self.sensorLock: - try: - start = time.clock() - image = wrappedAndor.getImage(self.width * self.height, .5) - getTime += (time.clock() - start) - except Exception, e: - if 'getImage failed' not in e: - print "Error in getImage:",e - # Probably a timeout; just try again. - continue - # \todo This timestamp is potentially bogus if we get behind in - # processing images. - timestamp = time.clock() + self.initialTimestamp -# print "Image has shape",image.shape,"min/max",image.min(),image.max() - start = time.clock() - image = self.fixImage(image) - fixTime += time.clock() - start - count += 1 - if count % 100 == 0: - # Periodically manually invoke the garbage collector, to - # ensure that we don't build up a giant pile of work that - # would interfere with our average write speed. - if gTime is None: - gTime = time.time() - delta = time.time() - gTime - print count, delta, getTime, fixTime, sendTime - gTime = time.time() - getTime = fixTime = sendTime = 0 - gc.collect() - - if self.shouldSendImages and self.clientConnection is not None: - try: - start = time.clock() - self.clientConnection.receiveData('new image', image, timestamp) - cost = time.clock() - start - if cost > .5: - print "Took %.2fs to send to client" % cost - sendTime += cost - except Exception, e: - print "Failed to send image to client: %s", e - traceback.print_exc() - - - ## Fix an image -- set its shape and apply any relevant correction. - def fixImage(self, image): - image.shape = self.height, self.width - if self.offsetImage is not None and self.offsetImage.shape == image.shape: - # Apply offset correction. - image -= self.offsetImage - return image - - - ## Update who we send image data to. - def setClient(self, connection): - self.clientConnection = connection - - - ## Update our image dimensions. - def setImageDimensions(self, width, height): - with self.sensorLock: - self.width = width - self.height = height - - - ## Update the image we use for offset correction. - def setOffsetCorrection(self, image): - self.offsetImage = image - - - ## Retrieve our offset correction image. - def getOffsetCorrection(self): - return self.offsetImage - - - -try: - cam = Camera() - daemon = Pyro4.Daemon(port = 7000, host = MY_IP_ADDRESS) - Pyro4.Daemon.serveSimple( - { - cam: 'Andorcam', - }, - daemon = daemon, ns = False, verbose = True - ) - -except Exception, e: - traceback.print_exc() - -del wrappedAndor # Clean up after ourselves. diff --git a/microscope/wavefront_sensors/memoryHandler.py b/microscope/wavefront_sensors/memoryHandler.py deleted file mode 100644 index c4a46297..00000000 --- a/microscope/wavefront_sensors/memoryHandler.py +++ /dev/null @@ -1,185 +0,0 @@ -''' -This program is intended to provide a pure-C "client" for interacting with -the camera, for use in debugging issues we've been having with running -long, high-speed acquisitions. It's basically a copy of the memhandler -code with some preambles and code to actually deal with the image data. -''' - -import AndorSDK3 as SDK3 -import threading -import Queue -import numpy as np -import ctypes -from time import sleep -from numpy import dtype - -class MemoryHandler(): - ''' - MemoryHandler doc - ''' - def __init__(self, handle): - self.handle = handle - self.buffersQueue = Queue.Queue() # Queue of buffers where images will be stored - self.imageBytes = 0 # Number of bytes used by one image - self.imagesQueue = Queue.Queue() # Queue of images waiting to be consumed - self.imageQueueLock = threading.Lock() # a lock for the image queue - self.readThread = threading.Thread() # initialize an empty thread. This one will be replaced as soon as first memory is allocated - self.readThreadLock = threading.Lock() # a lock for the readThread - self.shouldReadImages = False - - def stop(self): - ''' - Stops any active threads - ''' - # Halt any active read thread - if self.readThread.isAlive(): - with self.readThreadLock: - self.shouldReadImages = False - self.readThread.join() - - # Wipe the list of images waiting to be read out - with self.imageQueueLock: - while not self.imagesQueue.empty(): - self.imagesQueue.get() - - # Flush the camera buffer and wipe the buffersQueue, if it exists - if not self.buffersQueue.empty(): - try: - SDK3.Flush(self.handle) - except SDK3.CameraError as e: - print(e) - return 1 - while not self.buffersQueue.empty(): - self.buffersQueue.get() - - - def allocMemory(self, numBuffers, imageBytes, imageWidth, imageHeight, strides, timeout, dtype = ' Date: Wed, 14 Mar 2018 16:00:44 +0100 Subject: [PATCH 11/27] Softtrigger working --- doc/SERVERS | 2 +- microscope/wavefront_sensors/SID4_SDK.py | 56 ++-- microscope/wavefront_sensors/SID4_SDK_defs | 353 +++++++++++++++++++++ 3 files changed, 386 insertions(+), 25 deletions(-) create mode 100644 microscope/wavefront_sensors/SID4_SDK_defs diff --git a/doc/SERVERS b/doc/SERVERS index 2018523e..6b7a5e52 100644 --- a/doc/SERVERS +++ b/doc/SERVERS @@ -17,5 +17,5 @@ DEVICES = [ # device(TestCamera, '127.0.0.1', 8005, otherargs=1,), # device(TestLaser, '127.0.0.1', 8006), # device(TestFilterWheel, '127.0.0.1', 8007, - # filters=[(0, 'GFP', 525), (1, 'RFP'), (2, 'Cy5')]), + # filters=[(0, 'GFP', 525), (1, 'RFP'), (2, 'Cy5')]), ] diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 981825ba..82e668a6 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -223,9 +223,12 @@ def __init__(self, for key, exposure in EXPOSURE_TIMES.items(): self.exposure_time_s_to_index[exposure[1]] = key - # Create a queue to trigger software acquisitions + # TODO: Soft_trigger self._is_software_trigger = True - self._trigger_queue = queue.Queue() + # HACK: as fetching the data from the camera is triggering we use this + # variable to control that not the same data is acquired twice. Note the camera + # uses no buffers + self._valid_data = False # Add profile settings self.add_setting('user_profile_name', 'str', @@ -380,18 +383,12 @@ def get_camera_session(self): def invalidate_settings(self, func): """Wrap functions that invalidate settings so settings are reloaded.""" outerself = self + def wrapper(self, *args, **kwargs): func(self, *args, **kwargs) outerself._settings_valid = False return wrapper - @Pyro4.expose() - def soft_trigger(self): - if self._acquiring and self._is_software_trigger: - self._trigger_queue.put(1) - else: - raise Exception('cannot trigger if camera is not acquiring or is not in software trigger mode.') - def _create_buffers(self): """Creates a buffer to store the data. It also reloads all necessary parameters""" self._refresh_zernike_attributes() @@ -438,14 +435,9 @@ def _create_buffers(self): 'zernike_polynomials_bs': projection_coefficients_bs, 'np_zernike_polynomials': np_projection_coefficients}) - def _fetch_data(self, timeout=5, debug=False): + def _grab_live(self): """Uses the SDK's GrabLiveMode to get the phase and intensity maps and calls the Zernike functions to calculate the projection of the polynomials""" - if self._is_software_trigger: # TODO: fix this with a proper interrupt - if self._trigger_queue.empty(): - return None - self._trigger_queue.get() - try: self.SID4_SDK.GrabLiveMode(self.session_id, self.acquisition_buffer['phase_map'], @@ -456,7 +448,7 @@ def _fetch_data(self, timeout=5, debug=False): self.analysis_array_size, self.error_code) except: - print(self.error_code[0]) + self._logger.debug('Could not use GrabLiveMode. Error: ', self.error_code[0]) Exception('Could not GrabLiveMode') try: self.zernike_SDK.Zernike_PhaseProjection(self.acquisition_buffer['phase_map'], @@ -466,10 +458,25 @@ def _fetch_data(self, timeout=5, debug=False): self.acquisition_buffer['zernike_polynomials_bs'], self.error_code) except: + self._logger.debug('Could not use PhaseProjection. Error: ', self.error_code[0]) Exception('Could not get PhaseProjection') - if self._is_software_trigger: - self._trigger_queue.task_done() + @Pyro4.expose() + def soft_trigger(self): + if self._acquiring and self._is_software_trigger: + self._grab_live() + self._valid_data = True + else: + raise Exception('cannot trigger if camera is not acquiring or is not in software trigger mode.') + + def _fetch_data(self, timeout=5, debug=False): + if not self._is_software_trigger: # Camera is not using software trigger + self._grab_live() + else: # Camera is using software trigger: + if not self._valid_data: + return None + + self._valid_data = False return {'phase_map': np.copy(self.acquisition_buffer['np_phase_map']), 'intensity_map': np.copy(self.acquisition_buffer['np_intensity_map']), @@ -681,7 +688,6 @@ def _modify_user_profile(self, save=False): self.error_code) except: Exception('Could not modify user profile') - if save: self._save_user_profile() @@ -699,14 +705,16 @@ def _set_frame_rate(self, rate): if not self.error_code[0]: self.camera_information.FrameRate = rate - def _set_trigger_mode(self, mode): + def _set_trigger_mode(self, mode): # TODO: SoftTrigger self._set_camera_attribute('Trigger', mode) if not self.error_code[0]: self.camera_information.TriggerMode = mode if mode == 0: self._is_software_trigger = True + self._valid_data = False else: self._is_software_trigger = False + self._valid_data = False def _set_gain(self, gain): self._set_camera_attribute('Gain', gain) @@ -896,11 +904,11 @@ def _set_zernike_mask_col_size(self, col_size): wfs.enable() print('Current exposure_time: ', wfs.get_setting('exposure_time')) print('Changing exposure_time') - wfs.set_setting('exposure_time', 0) + wfs.set_setting('exposure_time', 2) print('Current exposure_time: ', wfs.get_setting('exposure_time')) - for i in range(10): - wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][15, 15]) + wfs.soft_trigger() + + print(wfs._fetch_data()['intensity_map'][15, 15]) print('Changing exposure_time') wfs.set_setting('exposure_time', 2) print('Current exposure_time: ', wfs.get_setting('exposure_time')) diff --git a/microscope/wavefront_sensors/SID4_SDK_defs b/microscope/wavefront_sensors/SID4_SDK_defs new file mode 100644 index 00000000..6d2883e6 --- /dev/null +++ b/microscope/wavefront_sensors/SID4_SDK_defs @@ -0,0 +1,353 @@ +//****************************************************************// +// This file is containing the headers from the SID4_SDK.h file // +// with some modifications. Namely #include and #ifdef have been // +// removed. Also, the typedef of LVBoolean has been changed // +//****************************************************************// + +#pragma pack(push) +#pragma pack(1) + +// In the original headers LVBoolean is defined as another type +typedef unsigned char LVBoolean; + +typedef int SDK_Reference; + +//****************************************************************// +// Definitions of structures used in the SID4_SDK functions // +//****************************************************************// + +// Tilt Information +typedef struct { + float XTilt; + float YTilt; + } TiltInfo; + +// Size Information on the 2D arrays given as input parameters +typedef struct { + long nRow; + long nCol; + } ArraySize; + + +// Analysis Information, to be used with GetUserProfile +typedef struct { + double GratingPositionMm; + double wavelengthNm; + LVBoolean RemoveBackgroundImage; + long PhaseSize_width; + long PhaseSize_Height; + } AnalysisInfo; + +// Camera Information, to be used with GetUserProfile +typedef struct { + long FrameRate; + unsigned long TriggerMode; + long Gain; + unsigned long ExposureTime; + float PixelSizeM; + unsigned char NumberOfCameraRecorded; + } CameraInfo; + +//**************************************************************// +// SID4_SDK Basic functions // +//**************************************************************// + + +// Configuration functions +void __cdecl OpenSID4(char UserProfileLocation[], SDK_Reference *SessionID, + long *ErrorCode); + +void __cdecl CloseSID4(SDK_Reference *SessionID, long *ErrorCode); + +void __cdecl GetUserProfile(SDK_Reference *SDKSessionID, char UserProfile_Name[], + long uspName_bufSize, char UserProfile_File[], long uspFile_bufSize, + char UserProfile_Description[], long uspDesc_bufSize, + char UsrP_LatestReference[], long uspLastRef_bufSize, + char UserProfile_Directory[], long uspDir_bufSize, char SDKVersion[], + long version_bufSize, AnalysisInfo *AnalysisInformation, CameraInfo *CameraInformation, + char SNPhasics[], long SNPhasics_bufSize, ArraySize *AnalysisArraySize, + long *ErrorCode); + +void __cdecl ChangeReference(SDK_Reference *SDKSessionID, char ReferencePath[], + unsigned short int ReferenceSource, char ArchivedPath[], long ArchivedPath_bufSize, + long *ErrorCode); + + void __cdecl SetBackground(SDK_Reference *SDKSessionID, unsigned short int Source, + char BackgroundFile[], char UpdatedBackgoundImageFile[], + long updatedImageFile_bufSize, long *ErrorCode); + +void __cdecl ChangeMask(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl LoadMaskDescriptorInfo(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl LoadMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + +void __cdecl ModifyUserProfile(SDK_Reference *SDKSessionID, + AnalysisInfo *AnalysisInformation, unsigned short int ReferenceSource, char ReferencePath[], + char UserProfile_Description[], LVBoolean *ReferenceChanged, + long *ErrorCode); + +void __cdecl NewUserProfile(SDK_Reference *SDKSessionID, char CameraSNPhasics[], + char ProfileName[], char UserProfileDirectory[], char ProfilePathFileOut[], + long pathFileOut_bufSize, long *ErrorCode); + +void __cdecl SaveCurrentUserProfile(SDK_Reference *SDKSessionID, + long *ErrorCode); + +void __cdecl SaveMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], + long ROI_GlobalRectangle[], long globalRect_bufSize, + unsigned short int ROI_NbOfContours, unsigned long ROI_Contours_info[], + long contoursInfo_bufSize, long ROI_Contours_coordinates[], + long contoursCoord_bufSize, long *ErrorCode); + + +// Camera control functions +void __cdecl StartLiveMode(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl StopLiveMode(SDK_Reference *SDKSessionID, long *ErrorCodeID); + +void __cdecl CameraInit(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraStart(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraStop(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraClose(SDK_Reference *SDKSessionID, long *ErrorCode); + +void __cdecl CameraList(SDK_Reference *SDKSessionID, char CameraList_SNPhasics[], + long camList_bufSize, long *ErrorCode); + +void __cdecl CameraSetup(SDK_Reference *SDKSessionID, unsigned short int CameraParameter, + unsigned long Value, long *ErrorCode); + +void __cdecl Camera_ConvertExposureMs(SDK_Reference *SDKSessionID, + double ExposureRawValueIn, double *ExposureValueMsOut, long *ErrorCode); +void __cdecl Camera_GetNumberOfAttribute(SDK_Reference *SDKSessionID, + long *NumberOfAttribute, long *ErrorCode); + +void __cdecl Camera_GetAttribute(SDK_Reference *SDKSessionID, + unsigned short int AttributeID, double *AttributeValueOut, long *ErrorCode); +void __cdecl Camera_SetAttribute(SDK_Reference *SDKSessionID, + unsigned short int AttributeID, double *AttributeValue, long *ErrorCode); + +void __cdecl Camera_GetAttributeList(SDK_Reference *SDKSessionID, + unsigned short int AttributeID[], long attribID_bufSize, + char AttributeName_SeparatedByTab[], long attribName_bufSize, + long AttributeGmin[], long attribGmin_bufSize, long AttributeGmax[], + long attribGmax_bufSize, long *ErrorCode); + +// Interferogram analysis functions +void __cdecl ArrayAnalysis(SDK_Reference *SDKSessionID, + short int InterferogramInArrayI16[], long Interfero_bufSize, + float Intensity[], long Intensity_bufSize, float Phase[], + long Phase_bufSize, TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, + ArraySize *ImageCameraSize, long *ErrorCode); + +void __cdecl FileAnalysis(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, + char InterferogramFile[], float Intensity[], long Intensity_bufSize, + float Phase[], long Phase_bufSize, TiltInfo *TiltInformation, + long *ErrorCode); + +void __cdecl GrabLiveMode(SDK_Reference *SDKSessionID, float Phase[], + long Phase_bufSize, float Intensity[], long Intensity_bufSize, + TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl GrabImage(SDK_Reference *SDKSessionID, short int Image[], + long Image_bufSize, ArraySize *ImageCameraSize, long *ErrorCode); + +void __cdecl Snap(SDK_Reference *SDKSessionID, float Phase[], + long Phase_bufSize, float Intensity[], long Intensity_bufSize, + TiltInfo *TiltInformation, long *ErrorCode); + +void __cdecl GrabToFile(SDK_Reference *SDKSessionID, unsigned long PaletteNumber, + char InterferogramFile[], LVBoolean *CheckOverWrite, long *ErrorCode); + +void __cdecl GetPhaseGradients(SDK_Reference *SDKSessionID, + ArraySize *AnalysisArraySize, float GradientX[], long GradX_bufSize, + float GradientY[], long GradY_bufSize, long *ErrorCode); + +void __cdecl SetIntegrationParam(SDK_Reference *SDKSessionID, + unsigned char Adv_Activation, unsigned short int Adv_Niter, float Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl GetQualityMap(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, + float QualityMap[], long qualityMap_bufSize, long *ErrorCode); + +void __cdecl GetIntegrationParam(SDK_Reference *SDKSessionID, + unsigned char *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl SetUnwrapParam(SDK_Reference *SDKSessionID, + unsigned short int UnwrappingAlgorithm, unsigned char UnwrappingOptions[], + long unwrapOptions_bufSize, long *ErrorCode); + +void __cdecl GetUnwrapParam(SDK_Reference *SDKSessionID, + unsigned short int *UnwrappingAlgoritm, unsigned char UnwrappingOptions[], + long unwrapOptions_bufSize, long *ErrorCode); + +void __cdecl getIntegrationParamOut(SDK_Reference *SDKSessionID, + LVBoolean *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, + long *ErrorCode); + +void __cdecl ADVTR_GetAnalysisArraySize(SDK_Reference *SDKSessionID, + double TR_AnalyseIn, ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl ADVTR_ComputeAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, + short int InterferogramI16[], long interfero_bufSize, double *TR_AnalyseOut, + long *ErrorCode); + +void __cdecl ADVTR_ArrayAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, + short int InterferogramI16[], long interfero_bufSize, double TR_AnalyseIn, + ArraySize *AnalysisArraySize, float Phase[], long phase_bufSize, + float Intensity[], long intensity_bufSize, TiltInfo *TiltInformation, + long *ErrorCode); + +void __cdecl GetImageInfo(SDK_Reference *SDKSessionID, char InterferogramFile[], + ArraySize *ImageSize, long *ErrorCode); + + +// Input-Output functions +void __cdecl LoadInterferogram(SDK_Reference *SDKSessionID, + char InterferogramFile[], ArraySize *ImageSize, short int InterferogramI16[], + long interfero_bufSize, long *ErrorCode); + +void __cdecl LoadMeasurementInfo(SDK_Reference *SDKSessionID, char PhaseFile[], + ArraySize *AnalysisArraySize, long *ErrorCode); + +void __cdecl LoadMeasurement(SDK_Reference *SDKSessionID, char PhaseFile[], + ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, + float Intensity[], long Intensity_bufSize, long *ErrorCode); + +void __cdecl SaveLastMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], + unsigned short int MeasurementList[], long measurementList_bufSize, + char MeasurementFilesOut[], long filesOut_bufSize, long *ErrorCode); + +void __cdecl SaveMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], + ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, + float Intensity[], long Intensity_bufSize, char PhaseFileOut[], + long phaseFileOut_bufSize, char IntensityFileOut[], + long intensityFileOut_bufSize, long *ErrorCode); + + +long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module); + + +//****************************************************************// +// Definitions of structures used in the Zernike_SDK functions // +//****************************************************************// + + +// Zernike Information structure giving on information on the projection base and the number of polynomials. +typedef struct { + unsigned short int Base; + long Polynomials; + } ZernikeInformation; + +//Zernike Parameters giving usefull information for the Zernike calculation +typedef struct { + unsigned long ImageRowSize; + unsigned long ImageColSize; + unsigned long MaskRowSize; + unsigned long MaskColSize; + unsigned short int Base; + } ZernikeParam; + +// Projection Basis Parameters, used with MET_GetProjectionBasisParameters +// defines the position and dimension of the pupil in the image array, +// as well as, the Basis type: Zernike or Legendre +typedef struct { + unsigned long PupilSize_Height; + unsigned long PupilSize_Width; + float PupilPosition_CH; + float PupilPosition_CW; + unsigned long ImageSize_Height; + unsigned long ImageSize_Width; + unsigned short Basis; + } ZKLParam; + +//**************************************************************// +// Zernike_SDK Basic functions // +//**************************************************************// + +/*! + * Zernike_PhaseFiltering + */ +void __cdecl Zernike_PhaseFiltering(float PhaseMap[], long phase_bufSize, + ArraySize *PhaseMapArraySize, double ProjectionCoefficients[], + long projCoef_bufSize, unsigned char PolyListToFilter[], + long polyList_bufSize, unsigned short int Filtering_Option, long *ErrorCode); +/*! + * Zernike_GetZernikeInfo + */ +void __cdecl Zernike_GetZernikeInfo(ZernikeInformation *ZernikeInfo, ArraySize *PhaseMapArraySize, + char ZernikeVersion[], long version_bufSize); +/*! + * Zernike_PhaseProjection + */ +void __cdecl Zernike_PhaseProjection(float PhaseMap[], long phase_bufSize, + ArraySize *PhaseMapArraySize, double ProjectionCoefficientsIn[], + long projCoef_bufSize, long *ErrorCode); +/*! + * Zernike_GetProjectionSet + */ +void __cdecl Zernike_GetProjectionSet(float ProjectionSetIn[], + long ZLprojSet_bufSize, ZernikeInformation *ZernikeInfo, ArraySize *PhaseMapArraySize, + long *ErrorCode); +/*! + * Zernike_UpdateProjection_fromUserProfile + */ +long __cdecl Zernike_UpdateProjection_fromUserProfile( + char UserProfileDirectory[], unsigned char PolynomialOrder, long *ErrorCode); +/*! + * Zernike_UpdateProjection_fromPhaseFile + */ +long __cdecl Zernike_UpdateProjection_fromPhaseFile(char PhaseFile[], + unsigned char PolynomialOrder, long *ErrorCode); +/*! + * Zernike_UpdateProjection_fromParameter + */ +long __cdecl Zernike_UpdateProjection_fromParameter(ZernikeParam *ZernkeParameters, + unsigned char PolynomialOrder, long *ErrorCode); + +/*! + * Zernike_UpdateProjection_fromParameters2 + * This function computes the Projection Basis (Zernike or Legendre) according + * the input parameters which are: + * - Dimension & Position of the analysis pupil + * - Dimension of the image that will contain the analysis pupil + * - Choice of the Basis that will be computed (Zernike or Legendre) + * + * If the input "Mask_Obstruction" array is not empty, the program will used + * it for computing the Projection Basis. In this case, the dimension of the + * "Mask_Obstruction" array should be identic to the image dimension specified + * in the ProjectionBasis_Parameters. + */ +void __cdecl Zernike_UpdateProjection_fromParameters2( + ZKLParam *ProjectionBasis_Parameters, float Mask_Obstruction[], + long mask_bufSize, ArraySize *MaskArraySize, unsigned char PolynomialsOrder, + long *ErrorCode); + +/*! + * Zernike_FreeMemory + */ +long __cdecl Zernike_FreeMemory(long *ErrorCode); +/*! + * Zernike_GetPolynomialsList + */ +void __cdecl Zernike_GetPolynomialsList(char PlynomialsList[], + long polyList_bufSize); + + +#pragma pack(pop) From 2f7c97c9be5951b9198df4e936e24109a4659c17 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Wed, 14 Mar 2018 18:03:38 +0100 Subject: [PATCH 12/27] Cleaned up WFS directories --- .../Examples/SID4_SDK_ChangeMask_Example.cpp | 203 ----------- .../Examples/SID4_SDK_ChangeMask_Example2.cpp | 260 -------------- .../SID4_SDK_FileAnalysis_Example.cpp | 119 ------- .../Examples/SID4_SDK_GrabImage_Example.cpp | 104 ------ .../Examples/SID4_SDK_OpenSID4_Example.cpp | 31 -- .../SID4_SDK_SaveMeasurement_Example.cpp | 137 -------- .../wavefront_sensors/SID4_SDK-cffiAPI.py | 318 ----------------- microscope/wavefront_sensors/SID4_SDK_cffi.py | 329 ------------------ 8 files changed, 1501 deletions(-) delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example2.cpp delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_FileAnalysis_Example.cpp delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp delete mode 100644 microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp delete mode 100644 microscope/wavefront_sensors/SID4_SDK-cffiAPI.py delete mode 100644 microscope/wavefront_sensors/SID4_SDK_cffi.py diff --git a/microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp deleted file mode 100644 index acf4a937..00000000 --- a/microscope/wavefront_sensors/Examples/SID4_SDK_ChangeMask_Example.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - © Copyright 2008 by PHASICS. - All rights reserved. - - @File: SID4_SDK_ChangeMask_Example.cpp - @Description: This example shows how to define the pupil analysis applied to - the interferogram by loading a mask file (.msk). - - This example involves 6 SID4 SDK's functions: - - "OpenSID4" loads the configuration file specified by "userProfile_File" in memory. - It returns a unique reference ID "SessionID" that should be used as an input - for all other SID4 SDK functions. - - "GetUserProfile" outputs the parameters currently used to analyse analyse interferograms - and the camera settings. - - "LoadMaskDescriptorInfo" returns the mask descriptor information from a "*.msk" file: - "Globalrectangle" gives the left, top, right and bottom position of the ROI global rectangle - "ROI_NbOfContours" indicates the number of sub-ROI defined in the chosen mask (main ROI). - "ROIinfo_Contours_info": array containing the sub-ROI characteristics, - there are three value for each one: - ID: this value refers to whether the contour is the external (0) or internal edge (1) - TypeValue: refers to the shape type of the contour: 3 = Rectangle, 4 = Oval or Circle - NumberOfCorrdinates: the number of points that defines the contour. - "ROIinfo_Contours_coordinates": array containing successively the coordinates of all sub-ROIs - - "ChangeMask" changes the current mask applied to the interferograms before any analysis. This - defines the analysis pupil. It can be changed by giving the path to a previously saved mask file - (.msk files saves with the phase and intensity files) or by giving manually the definition - of a Region of Interedt (ROI) which indicates the position and shape of the desired pupil. - - "FileAnalysis" analyses the interferogram. It ouputs a Phase and Intensity map, Tilt information - (X and Y tilts) removed from the ouput phase. - - "CloseSID4" closes the SID4 session. It releases memory devoted to the session. -*/ - - -#include -#include // for memory allocation -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -const int bufSize=1024; - - -void main(void) -{ - char MaskFile1[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask2.msk"; - char MaskFile2[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask3.msk"; - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; - SDK_Reference SessionID=0; - int i,nrow, ncol; - long Error=0; - char UserProfile_Name[bufSize]=""; - long uspName_bufSize = bufSize; - char UserProfile_File[bufSize]=""; - long uspFile_bufSize = bufSize; - char UserProfile_Description[bufSize]=""; - long uspDesc_bufSize = bufSize; - char UsrP_LastReference[bufSize]=""; - long uspLastRef_bufSize = bufSize; - char UserProfile_Directory[bufSize]=""; - long uspDir_bufSize = bufSize; - char SDKVersion[bufSize]=""; - long version_bufSize = bufSize; - AnalysisInfo AnalysisInformation; - CameraInfo CameraInformation; - char SNPhasics[bufSize]=""; - long SNPhasics_bufSize = bufSize; - ArraySize ImageSize; - - // Open SID4 Session - OpenSID4(userProfile_File,&SessionID,&Error); - if(!Error) - { printf ("************************ SID4 Session correctly opened **********************\n"); - printf ("SessionID=%d; Error=%d\n",SessionID,Error); - } - - // Reading of parameters currently used for interferogram analysis - GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, - UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, - UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, - &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); - - // Array dimension for Phase and Intensity - nrow = AnalysisInformation.PhaseSize_Height; - ncol = AnalysisInformation.PhaseSize_width; - - // memory allocation for Phase and Intensity before calling the FileAnalysis function - TiltInfo TiltInformation; - long Intensity_bufSize = nrow*ncol; - long Phase_bufSize = nrow*ncol; - ArraySize AnalysisArraySize; - - AnalysisArraySize.height=nrow; - AnalysisArraySize.width=ncol; - - float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); - float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); - - TiltInformation.XTilt=0; - TiltInformation.YTilt=0; - - - // Definition of the ROI Descriptor (Mask) which will be applied to the interferogram - // before any analysis - - //ROI Descriptor defintion - long ROI_GlobalRectangle[4]; //poition left, top, right and bottom side of the ROI global rectangle - long globalRect_bufSize = 4; // size of the ROI_GlobalRectangle array - unsigned short int ROI_NbOfContours; // gives the number of sub-ROI defined in the chosen mask (main ROI). - unsigned long ROIinfo_Contours_info[bufSize]; //array containing the sub-ROI characteristics, there are three value for each one: ID,TypeValue, NumberOfCorrdinates - long contoursInfo_bufSize = bufSize; - long ROIinfo_Contours_coordinates[bufSize]; //array containing successively the coordinates of all sub-ROIs - long contoursCoord_bufSize = bufSize; - - // we want to use the mask defined in the "Mask2.msk" file as the analysis pupil. - // Before to use the "ChangeMask" function to set the analysis pupil, it is necessary - // to get first the ROI descriptor information using the "LoadMaskDescriptorInfo" function - // in order to initialize the input parameters of the "ChangeMask" function. - - LoadMaskDescriptorInfo(&SessionID, MaskFile1, ROI_GlobalRectangle, globalRect_bufSize, - &ROI_NbOfContours, ROIinfo_Contours_info, contoursInfo_bufSize, ROIinfo_Contours_coordinates, - contoursCoord_bufSize, &Error); - - unsigned long *ROI_1_Contours_info =(unsigned long*)calloc(3*ROI_NbOfContours,sizeof(unsigned long)); - - int j=0; - int TotalNumberOfCoord = 0; - for(i=0;i -#include -#include // for memory allocation -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -const int bufSize=1024; - - -void main(void) -{ - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; - char MaskFile1[]="C:\\Program Files\\SID4_SDK\\Examples\\Mask files\\Mask2.msk"; - char MaskFile[]=""; - SDK_Reference SessionID=0; - int nrow, ncol; - long Error=0; - char UserProfile_Name[bufSize]=""; - long uspName_bufSize = bufSize; - char UserProfile_File[bufSize]=""; - long uspFile_bufSize = bufSize; - char UserProfile_Description[bufSize]=""; - long uspDesc_bufSize = bufSize; - char UsrP_LastReference[bufSize]=""; - long uspLastRef_bufSize = bufSize; - char UserProfile_Directory[bufSize]=""; - long uspDir_bufSize = bufSize; - char SDKVersion[bufSize]=""; - long version_bufSize = bufSize; - AnalysisInfo AnalysisInformation; - CameraInfo CameraInformation; - char SNPhasics[bufSize]=""; - long SNPhasics_bufSize = bufSize; - ArraySize ImageSize; - - // Open SID4 Session - OpenSID4(userProfile_File,&SessionID,&Error); - if(!Error) - { printf ("************************ SID4 Session correctly opened **********************\n"); - printf ("SessionID=%d; Error=%d\n",SessionID,Error); - } - else{ - printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); - exit(1); - } - - - // Definition of the ROI Descriptor (Mask) which will be applied to the interferogram - // before any analysis - - //ROI Descriptor defintion - long ROI_GlobalRectangle[4]; //poition left, top, right and bottom side of the ROI global rectangle - long globalRect_bufSize = 4; // size of the ROI_GlobalRectangle array - unsigned short int ROI_NbOfContours; // gives the number of sub-ROI defined in the chosen mask (main ROI). - unsigned long ROIinfo_Contours_info[bufSize]; //array containing the sub-ROI characteristics, there are three value for each one: ID,TypeValue, NumberOfCorrdinates - long contoursInfo_bufSize = bufSize; - long ROIinfo_Contours_coordinates[bufSize]; //array containing successively the coordinates of all sub-ROIs - long contoursCoord_bufSize = bufSize; - - // we want to use the mask defined in the "Mask2.msk" file as the analysis pupil. - // Before to use the "ChangeMask" function to set the analysis pupil, it is necessary - // to get first the ROI descriptor information using the "LoadMaskDescriptorInfo" function - // in order to initialize the input parameters of the "ChangeMask" function. - - LoadMaskDescriptorInfo(&SessionID, MaskFile1, ROI_GlobalRectangle, globalRect_bufSize, - &ROI_NbOfContours, ROIinfo_Contours_info, contoursInfo_bufSize, ROIinfo_Contours_coordinates, - contoursCoord_bufSize, &Error); - - unsigned long *ROI_1_Contours_info =(unsigned long*)calloc(3*ROI_NbOfContours,sizeof(unsigned long)); - - int i, j=0; - int TotalNumberOfCoord = 0; - for(i=0;i -#include -#include -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -const int bufSize=1024; - -void main(void) -{ - char userProfile_File[]="C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt"; - char inteferogram_File[]="C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Interferograms\\Interferogram.tif"; - SDK_Reference SessionID=0; - long nrow, ncol; - long Error=0; - char UserProfile_Name[bufSize]=""; - long uspName_bufSize = bufSize; - char UserProfile_File[bufSize]=""; - long uspFile_bufSize = bufSize; - char UserProfile_Description[bufSize]=""; - long uspDesc_bufSize = bufSize; - char UsrP_LastReference[bufSize]=""; - long uspLastRef_bufSize = bufSize; - char UserProfile_Directory[bufSize]=""; - long uspDir_bufSize = bufSize; - char SDKVersion[bufSize]=""; - long version_bufSize = bufSize; - AnalysisInfo AnalysisInformation; - CameraInfo CameraInformation; - char SNPhasics[bufSize]=""; - long SNPhasics_bufSize = bufSize; - ArraySize ImageSize; - - // Open SID4 Session - OpenSID4(userProfile_File,&SessionID,&Error); - if(!Error) - { printf ("************************ SID4 Session correctly opened **********************\n"); - printf ("SessionID=%d; Error=%d",SessionID,Error); - } - else{ - printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); - exit(1); - } - - // Reading of parameters currently used for interferogram analysis - GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, - UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, - UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, - &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); - - // Array dimension for Phase and Intensity - nrow = AnalysisInformation.PhaseSize_Height; - ncol = AnalysisInformation.PhaseSize_width; - - printf ("AnalysisInfo-H=%d\n",nrow); - printf ("AnalysisInfo-W=%d\n",ncol); - printf ("pixelsizeM=%g\n",CameraInformation.PixelSizeM); - - //// memory allocation for Phase and Intensity before calling FileAnalysis - TiltInfo TiltInformation; - long Intensity_bufSize = nrow*ncol; - long Phase_bufSize = nrow*ncol; - ArraySize AnalysisArraySize; - - AnalysisArraySize.nRow=nrow; - AnalysisArraySize.nCol=ncol; - - float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); - float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); - - - // Interferogram Analysis. We get in output Phase and Intensity map, tiltInformation - FileAnalysis(&SessionID, &AnalysisArraySize, inteferogram_File, Intensity, Intensity_bufSize, - Phase, Phase_bufSize, &TiltInformation, &Error); - if(!Error) - { printf ("\nXtilt=%f; Ytilt=%f\n",TiltInformation.XTilt,TiltInformation.YTilt); - } - else{ - printf ("\nThe error %d occured in the FileAnalysis function!\n\n",Error); - exit(1); - } - - // Close the SID4 session - CloseSID4(&SessionID,&Error); - - // Memory release - free(Intensity); - free(Phase); - -} - diff --git a/microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp deleted file mode 100644 index 22feead9..00000000 --- a/microscope/wavefront_sensors/Examples/SID4_SDK_GrabImage_Example.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - © Copyright 2008 by PHASICS. - All rights reserved. - - @File: SID4_SDK_GrabImage_Example.cpp - @Description: This example shows how to grab an interferogram from the current camera. - - 4 functions are involved: - - OpenSID4 loads the configuration file specified by "userProfile_File" in memory. - It returns a unique reference ID "SessionID" that should be used as an input - for all other SID4 SDK functions. - - GetUserProfile outputs the current camera settings. - - GrabImage grabs an interferogram from the camera. It initializes the camera according the - current camera settings, grabs an image (2D int16 Array) and stops the acquisition. - - CloseSID4 closes the SID4 session. It releases memory devoted to the session. -*/ - - -#include -#include -#include // for memory allocation -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -const int bufSize=1024; - - -void main(void) -{ - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - SDK_Reference SessionID=0; - int nrow, ncol; - long Error=0; - char UserProfile_Name[bufSize]=""; - long uspName_bufSize = bufSize; - char UserProfile_File[bufSize]=""; - long uspFile_bufSize = bufSize; - char UserProfile_Description[bufSize]=""; - long uspDesc_bufSize = bufSize; - char UsrP_LastReference[bufSize]=""; - long uspLastRef_bufSize = bufSize; - char UserProfile_Directory[bufSize]=""; - long uspDir_bufSize = bufSize; - char SDKVersion[bufSize]=""; - long version_bufSize = bufSize; - AnalysisInfo AnalysisInformation; - CameraInfo CameraInformation; - char SNPhasics[bufSize]=""; - long SNPhasics_bufSize = bufSize; - ArraySize ImageCameraSize; - - // Open SID4 Session - OpenSID4(userProfile_File,&SessionID,&Error); - if(!Error) - { printf ("************************ SID4 Session correctly opened **********************\n"); - printf ("SessionID=%d; Error=%d\n",SessionID,Error); - } - else{ - printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); - exit(1); - } - - // Reading of the current camera settings stored in the user profile - GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, - UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, - UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, - &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageCameraSize, &Error); - - if(!Error) - { - printf("\n**Camera settings** \n"); - printf ("PhasicsS/N=%s\n",SNPhasics); - printf ("FrameRate=%d\n",CameraInformation.FrameRate); - printf ("Gain=%d\n",CameraInformation.Gain); - printf ("ExposureTime=%d\n",CameraInformation.ExposureTime); - printf ("TriggerMode=%d\n",CameraInformation.TriggerMode); - } - - - nrow = ImageCameraSize.height; // 480 - ncol = ImageCameraSize.width; // 640 - - // Memory allocation of Image before calling GrabImage function - long Image_bufSize = nrow*ncol; - short int *Image = (short int*)malloc(sizeof(short int)* Image_bufSize); - - // Grab an image from camera - GrabImage(&SessionID, Image, Image_bufSize, &ImageCameraSize, &Error); - - if(!Error) - { printf("\n**Image content**\n"); - printf ("Image[0,0]=%d ",Image[0]); - } - - // Close the SID4 session - CloseSID4(&SessionID,&Error); - - // Release memory - free(Image); -} diff --git a/microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp deleted file mode 100644 index 3c8af35b..00000000 --- a/microscope/wavefront_sensors/Examples/SID4_SDK_OpenSID4_Example.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - © Copyright 2008 by PHASICS. - All rights reserved. - - @File: SID4_SDK_OpenSID4_Example.cpp - @Description: This example shows how to use OpenSID4() and CloseSID4() functions. - - OpenSID4 loads the configuration file specified by "userProfile_File" in memory. - It returns a unique reference ID "SessionID" that should be used as an input - for all other SID4 SDK functions. - - CloseSID4 closes the SID4 session. It releases memory devoted to the session. -*/ - -#include -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -void main(void) -{ - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - SDK_Reference SessionID=0; - long Error=0; - - OpenSID4(userProfile_File,&SessionID,&Error); - printf ("This example shows how to use OpenSID4() and CloseSID4() functions\n"); - printf ("SessionID=%d; Error=%d",SessionID,Error); - CloseSID4(&SessionID,&Error); - //getchar(); - printf("ok"); -} diff --git a/microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp b/microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp deleted file mode 100644 index abba5aca..00000000 --- a/microscope/wavefront_sensors/Examples/SID4_SDK_SaveMeasurement_Example.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - © Copyright 2008 by PHASICS. - All rights reserved. - - @File: SID4_SDK_SaveMeasurement_Example.cpp - @Description: This example shows how to save the Phase and Intensity maps. - - This example involves 5 SID4 SDK's functions: - - "OpenSID4" loads the configuration file specified by "userProfile_File" in memory. - It returns a unique reference ID "SessionID" that should be used as an input - for all other SID4 SDK functions. - - "GetUserProfile" outputs the parameters currently used to analyse analyse interferograms - and the camera settings. - - FileAnalysis analyses the interferogram. It ouputs a Phase and Intensity map, Tilt information - (X and Y tilts) removed from the ouput phase. - - "SaveMeasurement" saves the Phase and Intensity maps (2D single precision real array). - The filenames used are derived from the generic file name. The function adds a prefix "PHA" - for the phase file and a "INT" prfix for the intensity file. The arrays are saved in TIFF format. - The normalization information for the TIFF files are saved in a companion file, which prefix is "ACC". - - CloseSID4 closes the SID4 session. It releases memory devoted to the session. -*/ - - -#include -#include -#include // for memory allocation -#include "SID4_SDk.h" -#include "SID4_SDk_Constants.h" - -const int bufSize=1024; - - -void main(void) -{ - char userProfile_File[]="C:\\Program Files\\SID4_SDK\\Examples\\User Profile\\UserProfileExample.txt"; - char inteferogram_File[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Interferogram.tif"; - char GenericPath[]="C:\\Program Files\\SID4_SDK\\Examples\\Interferograms\\Result\\Interfo"; - SDK_Reference SessionID=0; - int nrow, ncol; - long Error=0; - char UserProfile_Name[bufSize]=""; - long uspName_bufSize = bufSize; - char UserProfile_File[bufSize]=""; - long uspFile_bufSize = bufSize; - char UserProfile_Description[bufSize]=""; - long uspDesc_bufSize = bufSize; - char UsrP_LastReference[bufSize]=""; - long uspLastRef_bufSize = bufSize; - char UserProfile_Directory[bufSize]=""; - long uspDir_bufSize = bufSize; - char SDKVersion[bufSize]=""; - long version_bufSize = bufSize; - AnalysisInfo AnalysisInformation; - CameraInfo CameraInformation; - char SNPhasics[bufSize]=""; - long SNPhasics_bufSize = bufSize; - ArraySize ImageSize; - - // Open SID4 Session - OpenSID4(userProfile_File,&SessionID,&Error); - if(!Error) - { printf ("************************ SID4 Session correctly opened **********************\n"); - printf ("SessionID=%d; Error=%d",SessionID,Error); - } - else{ - printf ("\nThe error %d occured in the OpenSID4 function!\n\n",Error); - exit(1); - } - - // Reading of parameters currently used for interferogram analysis - GetUserProfile(&SessionID, UserProfile_Name, uspName_bufSize, UserProfile_File, uspFile_bufSize, - UserProfile_Description, uspDesc_bufSize, UsrP_LastReference, uspLastRef_bufSize, - UserProfile_Directory, uspDir_bufSize,SDKVersion, version_bufSize, &AnalysisInformation, - &CameraInformation, SNPhasics, SNPhasics_bufSize, &ImageSize, &Error); - - // Array dimension for Phase and Intensity - nrow = AnalysisInformation.PhaseSize_Height; - ncol = AnalysisInformation.PhaseSize_width; - - //// memory allocation for Phase and Intensity before calling FileAnalysis - TiltInfo TiltInformation; - long Intensity_bufSize = nrow*ncol; - long Phase_bufSize = nrow*ncol; - ArraySize AnalysisArraySize; - - AnalysisArraySize.height=nrow; - AnalysisArraySize.width=ncol; - - float *Intensity = (float*)malloc(sizeof(float)* Intensity_bufSize); - float *Phase = (float*)malloc(sizeof(float)* Phase_bufSize); - - - // Interferogram Analysis. We get in output Phase and Intensity map, tiltInformation - FileAnalysis(&SessionID, &AnalysisArraySize, inteferogram_File, Intensity, Intensity_bufSize, - Phase, Phase_bufSize, &TiltInformation, &Error); - if(!Error) - { printf ("\nXtilt=%f; Ytilt=%f\n",TiltInformation.XTilt,TiltInformation.YTilt); - } - else{ - printf ("\nThe error %d occured in the FileAnalysis function!\n\n",Error); - exit(1); - } - - // Save the Phase and Intensity maps. The function returns the Intensity and Phase path files. - char PhaseFileOut[bufSize]=""; - long phaseFileOut_bufSize = bufSize; - char IntensityFileOut[bufSize]=""; - long intensityFileOut_bufSize = bufSize; - - SaveMeasurement(&SessionID, GenericPath, &AnalysisArraySize, Phase, Phase_bufSize, Intensity, - Intensity_bufSize, PhaseFileOut, phaseFileOut_bufSize, - IntensityFileOut, intensityFileOut_bufSize, &Error); - if(!Error) - { printf ("\nThe intensity map has been saved in the following file :\n"); - printf ("\n%s\n", IntensityFileOut); - printf ("\nThe phase map has been saved in the following file :\n"); - printf ("\n%s\n", PhaseFileOut); - } - else{ - printf ("\nThe error %d occured in the SaveMeasurement function!\n\n",Error); - exit(1); - } - - - // Close the SID4 session - CloseSID4(&SessionID,&Error); - - // Memory release - free(Intensity); - free(Phase); - -} diff --git a/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py b/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py deleted file mode 100644 index 01751033..00000000 --- a/microscope/wavefront_sensors/SID4_SDK-cffiAPI.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -# -# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -"""SID4_SDK wavefront sensor device. - -This class provides a wrapper for SID4's SDK interface that allows -a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. -""" - -from cffi import FFI - - -# We import the headers from a file in order to avoid copyright issues -header_path = 'C:\Program Files (x86)\SID4_SDK\DLL SDK\Headers\SID4_SDK.h' -dll_path = 'C:\Program Files (x86)\SID4_SDK\DLL SDK\BIN\SID4_SDK.dll' -cdef_from_file = '' - -try: - with open(header_path, 'r') as SDK_header: - cdef_from_file = SDK_header.read() -except FileNotFoundError: - print('Unable to find "%s" header file.' % header_path) - exit(1) -except IOError: - print('Unable to open "%s"' % header_path) - exit(2) -finally: - if cdef_from_file == '' or None: - print('File "%s" is empty' % header_path) - exit(3) - -ffibuilder = FFI() - -print(cdef_from_file[0:100]) - -ffibuilder.set_source("_SDK_SID4", - r""" // passed to the real C compiler - #include "C:\Program Files (x86)\SID4_SDK\DLL SDK\Headers\SID4_SDK.h" - """, - libraries=[]) # or a list of libraries to link with -# libraries=[dll_path]) - -# ''' -# Open VS prompt -# cd C:\Users\omxt\PycharmProjects\microscope\microscope\wavefront_sensors -# cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD^ -# -Ic:\Users\omxt\Applications\WinPython-32bit-3.6.1.0Qt5\python-3.6.1\include^ -# -Ic:\Users\omxt\Applications\WinPython-32bit-3.6.1.0Qt5\python-3.6.1\include^ -# "-IC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\ATLMFC\include"^ -# "-IC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\include"^ -# "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um"^ -# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\ucrt"^ -# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\shared"^ -# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\um"^ -# "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.15063.0\winrt"^ -# "-IC:\Program Files (x86)\SID4_SDK\DLL SDK\Headers"^ -# /Tc_SDK_SID4.c /Fo.\Release\_SDK_SID4.obj -# ''' - - -# ffibuilder.cdef(cdef_from_file) -ffibuilder.cdef(''' -#pragma pack(push) -#pragma pack(1) - -typedef unsigned char LVBoolean; - -typedef int SDK_Reference; - -//****************************************************************// -// Definitions of structures used in the SID4_SDK functions // -//****************************************************************// - -// Tilt Information -typedef struct { - float XTilt; - float YTilt; - } TiltInfo; - -// Size Information on the 2D arrays given as input parameters -typedef struct { - long nRow; - long nCol; - } ArraySize; - - -// Analysis Information, to be used with GetUserProfile -typedef struct { - double GratingPositionMm; - double wavelengthNm; - LVBoolean RemoveBackgroundImage; - long PhaseSize_width; - long PhaseSize_Height; - } AnalysisInfo; - -// Camera Information, to be used with GetUserProfile -typedef struct { - long FrameRate; - unsigned long TriggerMode; - long Gain; - unsigned long ExposureTime; - float PixelSizeM; - unsigned char NumberOfCameraRecorded; - } CameraInfo; - -//**************************************************************// -// SID4_SDK Basic functions // -//**************************************************************// - -// Configuration functions -void __cdecl OpenSID4(char UserProfileLocation[], SDK_Reference *SessionID, - long *ErrorCode); - -void __cdecl CloseSID4(SDK_Reference *SessionID, long *ErrorCode); - -void __cdecl GetUserProfile(SDK_Reference *SDKSessionID, char UserProfile_Name[], - long uspName_bufSize, char UserProfile_File[], long uspFile_bufSize, - char UserProfile_Description[], long uspDesc_bufSize, - char UsrP_LatestReference[], long uspLastRef_bufSize, - char UserProfile_Directory[], long uspDir_bufSize, char SDKVersion[], - long version_bufSize, AnalysisInfo *AnalysisInformation, CameraInfo *CameraInformation, - char SNPhasics[], long SNPhasics_bufSize, ArraySize *AnalysisArraySize, - long *ErrorCode); - -void __cdecl ChangeReference(SDK_Reference *SDKSessionID, char ReferencePath[], - unsigned short int ReferenceSource, char ArchivedPath[], long ArchivedPath_bufSize, - long *ErrorCode); - - void __cdecl SetBackground(SDK_Reference *SDKSessionID, unsigned short int Source, - char BackgroundFile[], char UpdatedBackgoundImageFile[], - long updatedImageFile_bufSize, long *ErrorCode); - -void __cdecl ChangeMask(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl LoadMaskDescriptorInfo(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl LoadMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl ModifyUserProfile(SDK_Reference *SDKSessionID, - AnalysisInfo *AnalysisInformation, unsigned short int ReferenceSource, char ReferencePath[], - char UserProfile_Description[], LVBoolean *ReferenceChanged, - long *ErrorCode); - -void __cdecl NewUserProfile(SDK_Reference *SDKSessionID, char CameraSNPhasics[], - char ProfileName[], char UserProfileDirectory[], char ProfilePathFileOut[], - long pathFileOut_bufSize, long *ErrorCode); - -void __cdecl SaveCurrentUserProfile(SDK_Reference *SDKSessionID, - long *ErrorCode); - -void __cdecl SaveMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -// Camera control functions -void __cdecl StartLiveMode(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl StopLiveMode(SDK_Reference *SDKSessionID, long *ErrorCodeID); - -void __cdecl CameraInit(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraStart(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraStop(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraClose(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraList(SDK_Reference *SDKSessionID, char CameraList_SNPhasics[], - long camList_bufSize, long *ErrorCode); - -void __cdecl CameraSetup(SDK_Reference *SDKSessionID, unsigned short int CameraParameter, - unsigned long Value, long *ErrorCode); - -void __cdecl Camera_ConvertExposureMs(SDK_Reference *SDKSessionID, - double ExposureRawValueIn, double *ExposureValueMsOut, long *ErrorCode); -void __cdecl Camera_GetNumberOfAttribute(SDK_Reference *SDKSessionID, - long *NumberOfAttribute, long *ErrorCode); - -void __cdecl Camera_GetAttribute(SDK_Reference *SDKSessionID, - unsigned short int AttributeID, double *AttributeValueOut, long *ErrorCode); -void __cdecl Camera_SetAttribute(SDK_Reference *SDKSessionID, - unsigned short int AttributeID, double *AttributeValue, long *ErrorCode); - -void __cdecl Camera_GetAttributeList(SDK_Reference *SDKSessionID, - unsigned short int AttributeID[], long attribID_bufSize, - char AttributeName_SeparatedByTab[], long attribName_bufSize, - long AttributeGmin[], long attribGmin_bufSize, long AttributeGmax[], - long attribGmax_bufSize, long *ErrorCode); - -// Interferogram analysis functions -void __cdecl ArrayAnalysis(SDK_Reference *SDKSessionID, - short int InterferogramInArrayI16[], long Interfero_bufSize, - float Intensity[], long Intensity_bufSize, float Phase[], - long Phase_bufSize, TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, - ArraySize *ImageCameraSize, long *ErrorCode); - -void __cdecl FileAnalysis(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, - char InterferogramFile[], float Intensity[], long Intensity_bufSize, - float Phase[], long Phase_bufSize, TiltInfo *TiltInformation, - long *ErrorCode); - -void __cdecl GrabLiveMode(SDK_Reference *SDKSessionID, float Phase[], - long Phase_bufSize, float Intensity[], long Intensity_bufSize, - TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl GrabImage(SDK_Reference *SDKSessionID, short int Image[], - long Image_bufSize, ArraySize *ImageCameraSize, long *ErrorCode); - -void __cdecl Snap(SDK_Reference *SDKSessionID, float Phase[], - long Phase_bufSize, float Intensity[], long Intensity_bufSize, - TiltInfo *TiltInformation, long *ErrorCode); - -void __cdecl GrabToFile(SDK_Reference *SDKSessionID, unsigned long PaletteNumber, - char InterferogramFile[], LVBoolean *CheckOverWrite, long *ErrorCode); - -void __cdecl GetPhaseGradients(SDK_Reference *SDKSessionID, - ArraySize *AnalysisArraySize, float GradientX[], long GradX_bufSize, - float GradientY[], long GradY_bufSize, long *ErrorCode); - -void __cdecl SetIntegrationParam(SDK_Reference *SDKSessionID, - unsigned char Adv_Activation, unsigned short int Adv_Niter, float Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl GetQualityMap(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, - float QualityMap[], long qualityMap_bufSize, long *ErrorCode); - -void __cdecl GetIntegrationParam(SDK_Reference *SDKSessionID, - unsigned char *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl SetUnwrapParam(SDK_Reference *SDKSessionID, - unsigned short int UnwrappingAlgorithm, unsigned char UnwrappingOptions[], - long unwrapOptions_bufSize, long *ErrorCode); - -void __cdecl GetUnwrapParam(SDK_Reference *SDKSessionID, - unsigned short int *UnwrappingAlgoritm, unsigned char UnwrappingOptions[], - long unwrapOptions_bufSize, long *ErrorCode); - -void __cdecl getIntegrationParamOut(SDK_Reference *SDKSessionID, - LVBoolean *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl ADVTR_GetAnalysisArraySize(SDK_Reference *SDKSessionID, - double TR_AnalyseIn, ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl ADVTR_ComputeAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, - short int InterferogramI16[], long interfero_bufSize, double *TR_AnalyseOut, - long *ErrorCode); - -void __cdecl ADVTR_ArrayAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, - short int InterferogramI16[], long interfero_bufSize, double TR_AnalyseIn, - ArraySize *AnalysisArraySize, float Phase[], long phase_bufSize, - float Intensity[], long intensity_bufSize, TiltInfo *TiltInformation, - long *ErrorCode); - -void __cdecl GetImageInfo(SDK_Reference *SDKSessionID, char InterferogramFile[], - ArraySize *ImageSize, long *ErrorCode); - - -// Input-Output functions -void __cdecl LoadInterferogram(SDK_Reference *SDKSessionID, - char InterferogramFile[], ArraySize *ImageSize, short int InterferogramI16[], - long interfero_bufSize, long *ErrorCode); - -void __cdecl LoadMeasurementInfo(SDK_Reference *SDKSessionID, char PhaseFile[], - ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl LoadMeasurement(SDK_Reference *SDKSessionID, char PhaseFile[], - ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, - float Intensity[], long Intensity_bufSize, long *ErrorCode); - -void __cdecl SaveLastMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], - unsigned short int MeasurementList[], long measurementList_bufSize, - char MeasurementFilesOut[], long filesOut_bufSize, long *ErrorCode); - -void __cdecl SaveMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], - ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, - float Intensity[], long Intensity_bufSize, char PhaseFileOut[], - long phaseFileOut_bufSize, char IntensityFileOut[], - long intensityFileOut_bufSize, long *ErrorCode); - - - -long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module); - -#pragma pack(pop) -''') - -if __name__ == "__main__": - ffibuilder.compile(verbose=True) \ No newline at end of file diff --git a/microscope/wavefront_sensors/SID4_SDK_cffi.py b/microscope/wavefront_sensors/SID4_SDK_cffi.py deleted file mode 100644 index 35976612..00000000 --- a/microscope/wavefront_sensors/SID4_SDK_cffi.py +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -# -# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -"""SID4_SDK wavefront sensor device. - -This class provides a wrapper for SID4's SDK interface that allows -a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. -""" - -from cffi import FFI - - - -header_defs = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" -header_path = "C:\\Program Files (x86)\\SID4_SDK\\DLL SDK\\Headers\\SID4_SDK.h" -cdef_from_file = '' - -try: - with open(header_defs, 'r') as header_defs: - cdef_from_file = header_defs.read() -except FileNotFoundError: - print('Unable to find "%s" header file.' % header_defs) - exit(1) -except IOError: - print('Unable to open "%s"' % header_defs) - exit(2) -finally: - if cdef_from_file == '' or None: - print('File "%s" is empty' % header_defs) - exit(3) - -ffi = FFI() - -ffi.cdef(cdef_from_file) - -SDK = ffi.dlopen("SID4_SDK.dll") -ZernikeSDK = ffi.dlopen("Zernike_SDK.dll") - -buffer_size = 1024 - -user_profile_file_in = ffi.new("char[]", buffer_size) -user_profile_file_in_bs = ffi.cast("long", buffer_size) -user_profile_file_in = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' - -interferogram_file = ffi.new("char[]", buffer_size) -interferogram_file_bs = ffi.cast("long", buffer_size) -interferogram_file = "C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Interferograms\\Interferogram.tif" - -session_id = ffi.new('SDK_Reference *', 0) - -error_code = ffi.new('long *', 0) - -user_profile_name = ffi.new("char[]", buffer_size) # initialize first with a certain buffer size -user_profile_name_bs = ffi.cast("long", buffer_size) - -user_profile_file_out = ffi.new("char[]", buffer_size) -user_profile_file_out_bs = ffi.cast("long", buffer_size) - -user_profile_description = ffi.new("char[]", buffer_size) -user_profile_description_bs = ffi.cast("long", buffer_size) - -user_profile_last_reference = ffi.new("char[]", buffer_size) -user_profile_last_reference_bs = ffi.cast("long", buffer_size) - -user_profile_last_directory = ffi.new("char[]", buffer_size) -user_profile_last_directory_bs = ffi.cast("long", buffer_size) - -sdk_version = ffi.new("char[]", buffer_size) -sdk_version_bs = ffi.cast("long", buffer_size) - -analysis_information = ffi.new("AnalysisInfo *") - -camera_information = ffi.new("CameraInfo *") - -camera_sn = ffi.new("char[]", buffer_size) -camera_sn_bs = ffi.cast("long", buffer_size) - -image_size = ffi.new("ArraySize *") - -tilt_information = ffi.new('TiltInfo *') -analysis_array_size = ffi.new('ArraySize *', [80,80]) - -print('Opening SDK...') -SDK.OpenSID4(user_profile_file_in, session_id, error_code) -print('Session ID:') -print(session_id[0]) -print('Error code:') -print(error_code[0]) - -SDK.GetUserProfile(session_id, - user_profile_name, - user_profile_name_bs, - user_profile_file_out, - user_profile_file_out_bs, - user_profile_description, - user_profile_description_bs, - user_profile_last_reference, - user_profile_last_reference_bs, - user_profile_last_directory, - user_profile_last_directory_bs, - sdk_version, - sdk_version_bs, - analysis_information, - camera_information, - camera_sn, - camera_sn_bs, - image_size, - error_code) - -print('Initializing WFS...') -SDK.CameraInit(session_id, error_code) -print(error_code[0]) - -##print('Starting WFS...') -##SDK.CameraStart(session_id, error_code) -##print(error_code[0]) -## -##print('Stoping WFS...') -##SDK.CameraStop(session_id, error_code) -##print(error_code[0]) - - -ffi.string(user_profile_name) -int(user_profile_name_bs) - -print('Grating Position', analysis_information.GratingPositionMm) -print('wavelengthNm', analysis_information.wavelengthNm) -print('RemoveBackgroundImage', analysis_information.RemoveBackgroundImage) -print('Analysis information PhaseSize_Height:') -print(int.from_bytes(ffi.buffer(analysis_information)[21:25], 'little')) -print('Analysis information PhaseSize_width:') -print(int.from_bytes(ffi.buffer(analysis_information)[17:21], 'little')) - -print('FrameRate', camera_information.FrameRate) -print('TriggerMode', camera_information.TriggerMode) -print('gain', camera_information.Gain) -print('Exp Time', camera_information.ExposureTime) -print('Pixel Size', camera_information.PixelSizeM) -print('NumberOfCameraRecorded', camera_information.NumberOfCameraRecorded) - -nr_of_attributes = ffi.new('long *') -SDK.Camera_GetNumberOfAttribute(session_id, nr_of_attributes, error_code) - -print('nr of attributes is: ') -print(nr_of_attributes[0]) - -attribute_id = ffi.new('unsigned short int[]', nr_of_attributes[0]) -attribute_id_bs = ffi.cast('long', nr_of_attributes[0]) -attribute_name = ffi.new('char[]', buffer_size) -attribute_name_bs = ffi.cast('long', buffer_size) -attribute_min = ffi.new('long[]', nr_of_attributes[0]) -attribute_min_bs = ffi.cast('long', nr_of_attributes[0] * 4) -attribute_max = ffi.new('long[]', nr_of_attributes[0]) -attribute_max_bs = ffi.cast('long', nr_of_attributes[0] * 4) - -print('Getting camera attributes') -SDK.Camera_GetAttributeList(session_id, - attribute_id, - attribute_id_bs, - attribute_name, - attribute_name_bs, - attribute_min, - attribute_min_bs, - attribute_max, - attribute_max_bs, - error_code) -print(error_code[0]) - -print('Attributes ids:') -print(ffi.unpack(attribute_id, nr_of_attributes[0])) -print('Names:') -print(ffi.string(attribute_name)) -print('Min values:') -print(ffi.unpack(attribute_min, nr_of_attributes[0])) -print('Max values:') -print(ffi.unpack(attribute_max, nr_of_attributes[0])) - -phase = ffi.new('float[]', 6400) -phase_bs = ffi.cast('long', 25600) -intensity = ffi.new('float[]', 6400) -intensity_bs = ffi.cast('long', 25600) - -image = ffi.new("short int[307200]") -image_bs = ffi.cast("long", 614400) - -print('Starting Live mode...') -SDK.StartLiveMode(session_id, error_code) -print(error_code[0]) - -# print('Grabbing image...') -# SDK.GrabImage(session_id, image, image_bs, camera_array_size, error_code) -# print(error_code[0]) -# -# print('Part of the image') -# print([x for x in image[0:20]]) - -print('Grabbing Live mode...') -SDK.GrabLiveMode(session_id, - phase, - phase_bs, - intensity, - intensity_bs, - tilt_information, - analysis_array_size, - error_code) -print(error_code[0]) - -print('creating zernike parameters') -zernike_parameters = ffi.new("ZernikeParam *") -zernike_information = ffi.new("ZernikeInformation *") -phase_map_array_size = ffi.new("ArraySize *") -zernike_version = ffi.new("char[]", buffer_size) -zernike_version_bs = ffi.cast("long", buffer_size) - -proj_coefficients = ffi.new("double[]", 253) -proj_coefficients_bs = ffi.cast("long", 2024) -max_nr_polynomials = ffi.cast("unsigned char", 21) - -print('Populating zernike parameters') -ZernikeSDK.Zernike_GetZernikeInfo(zernike_information, - phase_map_array_size, - zernike_version, - zernike_version_bs) - -print('Changing zernike parameters') -# # Using Zernike_UpdateProjection_fromUserProfile: updates analysis array size but not nr of polynomials -# ZernikeSDK.Zernike_UpdateProjection_fromUserProfile(user_profile_file_out, -# max_nr_polynomials, -# error_code) -# -# # Using Zernike_UpdateProjection_fromArray: Not in library!!! -# ZernikeSDK.Zernike_UpdateProjection_fromArray(phase, -# phase_bs, -# analysis_array_size, -# max_nr_polynomials, -# error_code) -# -# Using Zernike_UpdateProjection_fromParameter: updates analysis array size but not nr of polynomials -zernike_parameters.ImageRowSize = 80 -zernike_parameters.ImageColSize = 80 -zernike_parameters.MaskRowSize = 80 -zernike_parameters.MaskColSize = 80 -zernike_parameters.Base = 0 -ZernikeSDK.Zernike_UpdateProjection_fromParameter(zernike_parameters, - max_nr_polynomials, - error_code) - -# Using Zernike_UpdateProjection_fromParameters2 - -''' -/*! - * Zernike_UpdateProjection_fromParameters2 - * This function computes the Projection Basis (Zernike or Legendre) according - * the input parameters which are: - * - Dimension & Position of the analysis pupil - * - Dimension of the image that will contain the analysis pupil - * - Choice of the Basis that will be computed (Zernike or Legendre) - * - * If the input "Mask_Obstruction" array is not empty, the program will used - * it for computing the Projection Basis. In this case, the dimension of the - * "Mask_Obstruction" array should be identic to the image dimension specified - * in the ProjectionBasis_Parameters. - */ -void __cdecl Zernike_UpdateProjection_fromParameters2( - ZKLParam *ProjectionBasis_Parameters, float Mask_Obstruction[], - long mask_bufSize, ArraySize *MaskArraySize, unsigned char PolynomialsOrder, - long *ErrorCode); -''' -# -# # Using Zernike_UpdateProjection_fromPhaseFile -# phase_file = ffi.new("char[]", buffer_size) -# phase_file = b'C:\\Program Files (x86)\\SID4_SDK\\Examples\\Labview\\Results\\PHA example.tif' -# -# ZernikeSDK.Zernike_UpdateProjection_fromPhaseFile(phase_file, -# max_nr_polynomials, -# error_code) -# -print(error_code[0]) - -# zernike_information.Polynomials = 5 - -print('Getting parameters back') -ZernikeSDK.Zernike_GetZernikeInfo(zernike_information, - phase_map_array_size, - zernike_version, - zernike_version_bs) - -print('Getting polynomials...') -ZernikeSDK.Zernike_PhaseProjection(phase, - phase_bs, - analysis_array_size, - proj_coefficients, - proj_coefficients_bs, - error_code) -# -print(error_code[0]) - -## -##print('Part of the phase') -##print([x for x in phase[0:20]]) -## -##print('Part of the intensity') -##print([x for x in intensity[0:20]]) -## -print('Stopping Live mode...') -SDK.StopLiveMode(session_id, error_code) -print(error_code[0]) - -print('Closing camera...') -SDK.CameraClose(session_id, error_code) -print(error_code[0]) - -print('Closing SDK...') -SDK.CloseSID4(session_id, error_code) -print(error_code[0]) From b4d3d4167ae3035a5cd568db1875c2e5dfedc24f Mon Sep 17 00:00:00 2001 From: juliomateoslangerak Date: Wed, 14 Mar 2018 19:04:51 +0100 Subject: [PATCH 13/27] Implemented support for Obis laser. Not tested --- doc/supported-devices.rst | 1 + microscope/lasers/obis.py | 177 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 microscope/lasers/obis.py diff --git a/doc/supported-devices.rst b/doc/supported-devices.rst index 32830b04..5c7dc483 100644 --- a/doc/supported-devices.rst +++ b/doc/supported-devices.rst @@ -28,3 +28,4 @@ Lasers - Cobolt (:class:`microscope.lasers.cobolt`) - Deepstar (:class:`microscope.lasers.deepstar`) - Sapphire (:class:`microscope.lasers.saphhire`) +- Obis (:class:`microscope.lasers.obis`) diff --git a/microscope/lasers/obis.py b/microscope/lasers/obis.py new file mode 100644 index 00000000..7e758a98 --- /dev/null +++ b/microscope/lasers/obis.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# -*- coding: utf-8 +# +# Copyright 2016 Mick Phillips (mick.phillips@gmail.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import serial +import threading +# import time +from microscope.devices import LaserDevice +import functools + + +def lock_comms(func): + """A decorator to flush the input buffer prior to issuing a command. + + Locks the comms channel so that a function must finish all its comms + before another can run. + """ + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + with self.comms_lock: + return func(self, *args, **kwargs) + + return wrapper + + +class ObisLaser(LaserDevice): + """A class to control OBIS lasers from Coherent""" + def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): + super(ObisLaser, self).__init__() + self.connection = serial.Serial(port=com, + baudrate=baud, + timeout=timeout, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS, + parity=serial.PARITY_NONE) + # Start a logger. + response = self.send('SYSTem:INFormation:MODel?') + self._logger.info('OBIS laser model: [%s]' % response) + response = self.send('SYSTem:INFormation:SNUMber?') + self._logger.info('OBIS laser serial number: [%s]' % response) + response = self.send('SYSTem:INFormation:SNUMber?') + self._logger.info('OBIS laser serial number: [%s]' % response) + response = self.send('SYSTem:CDRH?') + self._logger.info('CDRH safety: [%s]' % response) + response = self.send('SOURce:TEMPerature:APRobe?') + self._logger.info('TEC temperature control: [%s]' % response) + response = self.send('*TST?') + self._logger.info('Self test procedure: [%s]' % response) + + # We need to ensure that autostart is disabled so that we can switch emission + # on/off remotely. + response = self.send('SYSTem:AUTostart?') + self._logger.info('Response to Autostart: [%s]' % response) + self.comms_lock = threading.RLock() + + def send(self, command): + """Send command and retrieve response.""" + self._write(str(command)) + return self._readline() + + @lock_comms + def clear_fault(self): + self.send('SYSTem:ERRor:CLEar') + return self.get_status() + + def flush_buffer(self): + line = ' ' + while len(line) > 0: + line = self._readline() + + @lock_comms + def is_alive(self): + response1 = self.send('SOURce:AM:STATe?') + response2 = self.send('SOURce:TEMPerature:APRobe?') + return response1 and response2 == 'ON' + + @lock_comms + def get_status(self): + result = [] + for cmd, stat in [('SOURce:AM:STATe?', 'Emission on?'), + ('SOURce:POWer:LEVel:IMMediate:AMPLitude?', 'Target power:'), + ('SOURce:POWer:LEVel?', 'Measured power:'), + ('SYSTem:STATus?', 'Status code?'), + ('SYSTem:FAULt?', 'Fault code?'), + ('SYSTem:HOURs?', 'Head operating hours:')]: + result.append(stat + ' ' + self.send(cmd)) + return result + + @lock_comms + def _on_shutdown(self): + # Disable laser. + self.disable() + + @lock_comms + def initialize(self): + """Initialization to do when cockpit connects.""" + self.flush_buffer() + # We don't want 'direct control' mode. + self.send('SOURce:AM:EXTernal DIGital') # Change to MIXED when analogue output is available + + @lock_comms + def enable(self): + """Turn the laser ON. Return True if we succeeded, False otherwise.""" + self._logger.info('Turning laser ON.') + # Exiting Sleep Mode. + self.send('SOURce:TEMPerature:APRobe ON') + # Turn on emission. + self.send('SOURce:AM:STATe ON') + response = self.send('SOURce:AM:STATe?') + self._logger.info("SOURce:AM:STATe? [%s]" % response) + + if not self.get_is_on(): + # Something went wrong. + self._logger.error("Failed to turn ON. Current status:\r\n") + self._logger.error(self.get_status()) + return False + return True + + @lock_comms + def disable(self): + """Turn the laser OFF. Return True if we succeeded, False otherwise.""" + self._logger.info('Turning laser OFF.') + # Turning LASER OFF + self.send('SOURce:AM:STATe OFF') + # Going into Sleep mode + self.send('SOURce:TEMPerature:APRobe OFF') + + if self.get_is_on(): + # Something went wrong. + self._logger.error("Failed to turn OFF. Current status:\r\n") + self._logger.error(self.get_status()) + return False + return True + + @lock_comms + def get_is_on(self): + """Return True if the laser is currently able to produce light.""" + response = self.send('SOURce:AM:STATe?') + return response == 'ON' + + @lock_comms + def get_max_power_mw(self): + """Gets the maximum laser power in mW.""" + power_w = self.send('SYSTem:INFormation:POWer?') + return float(power_w / 1000) + + @lock_comms + def get_power_mw(self): + """Gets current power level in mW""" + if not self.get_is_on(): + return 0.0 + power_w = self.send('SOURce:POWer:LEVel?') + return 1000 * float(power_w) + + @lock_comms + def _set_power_mw(self, mW): + mW = min(mW, self.get_max_power_mw) + self._logger.info("Setting laser power to %.4fW." % (mW * 1000.0)) + self.send("SOURce:POWer:LEVel:IMMediate:AMPLitude %.4f" % mW * 1000.0) + + @lock_comms + def get_set_power_mw(self): + power_w = self.send('SOURce:POWer:LEVel:IMMediate:AMPLitude?') + return 1000 * float(power_w) From b64a95d3bbf75842ee8c87c90d0a3a5d575a03c9 Mon Sep 17 00:00:00 2001 From: Julio Mateos Date: Thu, 15 Mar 2018 16:04:46 +0100 Subject: [PATCH 14/27] RMS and PtoV calculated on pupil data. Zeros outside of the array are trimmed --- microscope/wavefront_sensors/SID4_SDK.py | 45 ++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py index 82e668a6..f12a75cf 100644 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ b/microscope/wavefront_sensors/SID4_SDK.py @@ -497,12 +497,13 @@ def _process_data(self, data): - PtoV: peak to valley measurement - zernike_polynomials: a list with the relevant Zernike polynomials """ + trimmed_phase_map = self._trim_zeros(data['phase_map']) processed_data = {'intensity_map': self._apply_transform(data['intensity_map']), 'phase_map': self._apply_transform(data['phase_map']), 'tilts': data['tilts'], 'zernike_polynomials': data['zernike_polynomials'], - 'RMS': data['phase_map'].std(), - 'PtoV': data['phase_map'].ptp()} + 'RMS': trimmed_phase_map.std(), + 'PtoV': trimmed_phase_map.ptp()} return processed_data @@ -518,6 +519,13 @@ def _apply_transform(self, array): (1, 1): np.fliplr(np.flipud(np.rot90(array, rot))) }[flips] + def _trim_zeros(self, data): + """Returns a linear numpy array where the zeros outside the pupil have been trimmed""" + trimmed_data = np.array([]) + for row in data: + trimmed_data = np.append(trimmed_data, np.trim_zeros(row)) + return trimmed_data + def abort(self): """Abort acquisition.""" self._logger.debug('Aborting acquisition.') @@ -902,6 +910,39 @@ def _set_zernike_mask_col_size(self, col_size): wfs = SID4Device() wfs.initialize() wfs.enable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.disable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.enable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.disable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.shutdown() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.initialize() + wfs.enable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.disable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.shutdown() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + wfs.enable() + print('Enabled: ' + str(wfs.get_is_enabled())) + print('Acquiring: ' + str(wfs._acquiring)) + + for i in range(3): + wfs.soft_trigger() + + + print('Current exposure_time: ', wfs.get_setting('exposure_time')) print('Changing exposure_time') wfs.set_setting('exposure_time', 2) From 2d97002b3369ae014bc64f20d7e5c849649cca3f Mon Sep 17 00:00:00 2001 From: juliomateoslangerak Date: Fri, 16 Mar 2018 15:29:20 +0100 Subject: [PATCH 15/27] Commands tested --- microscope/lasers/obis.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/microscope/lasers/obis.py b/microscope/lasers/obis.py index 7e758a98..98783abb 100644 --- a/microscope/lasers/obis.py +++ b/microscope/lasers/obis.py @@ -51,8 +51,6 @@ def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): self._logger.info('OBIS laser model: [%s]' % response) response = self.send('SYSTem:INFormation:SNUMber?') self._logger.info('OBIS laser serial number: [%s]' % response) - response = self.send('SYSTem:INFormation:SNUMber?') - self._logger.info('OBIS laser serial number: [%s]' % response) response = self.send('SYSTem:CDRH?') self._logger.info('CDRH safety: [%s]' % response) response = self.send('SOURce:TEMPerature:APRobe?') @@ -155,7 +153,7 @@ def get_is_on(self): def get_max_power_mw(self): """Gets the maximum laser power in mW.""" power_w = self.send('SYSTem:INFormation:POWer?') - return float(power_w / 1000) + return float(power_w * 1000) @lock_comms def get_power_mw(self): @@ -168,8 +166,8 @@ def get_power_mw(self): @lock_comms def _set_power_mw(self, mW): mW = min(mW, self.get_max_power_mw) - self._logger.info("Setting laser power to %.4fW." % (mW * 1000.0)) - self.send("SOURce:POWer:LEVel:IMMediate:AMPLitude %.4f" % mW * 1000.0) + self._logger.info("Setting laser power to %.4fW." % (mW / 1000.0)) + self.send("SOURce:POWer:LEVel:IMMediate:AMPLitude %.4f" % mW / 1000.0) @lock_comms def get_set_power_mw(self): From e00cab4c42cbed4b9bf25672012984b411d4a584 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Wed, 18 Jul 2018 12:49:27 +0200 Subject: [PATCH 16/27] Adds support for Coherent OBIS lasers Tested --- doc/supported-devices.rst | 2 +- microscope/devices.py | 1 - microscope/lasers/obis.py | 235 +++++++++++++++++++++----------------- 3 files changed, 129 insertions(+), 109 deletions(-) diff --git a/doc/supported-devices.rst b/doc/supported-devices.rst index 46590aff..200cd720 100644 --- a/doc/supported-devices.rst +++ b/doc/supported-devices.rst @@ -31,6 +31,6 @@ Lasers ------ - Cobolt (:class:`microscope.lasers.cobolt`) -- Obis (:class:`microscope.lasers.obis`) +- Coherent Obis (:class:`microscope.lasers.obis`) - Coherent Sapphire (:class:`microscope.lasers.saphhire`) - Omicron Deepstar (:class:`microscope.lasers.deepstar`) diff --git a/microscope/devices.py b/microscope/devices.py index 3bd1d527..d62ae62d 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -299,7 +299,6 @@ def update_settings(self, incoming, init=False): results[key] = self.settings[key]['get']() return results - def keep_acquiring(func): """Wrapper to preserve acquiring state of data capture devices.""" def wrapper(self, *args, **kwargs): diff --git a/microscope/lasers/obis.py b/microscope/lasers/obis.py index 98783abb..6b08c7c6 100644 --- a/microscope/lasers/obis.py +++ b/microscope/lasers/obis.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 # # Copyright 2016 Mick Phillips (mick.phillips@gmail.com) +# Copyright 2018 David Pinto +# Copyright 2018 Julio Mateos Langerak # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,31 +17,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import serial -import threading -# import time -from microscope.devices import LaserDevice -import functools - -def lock_comms(func): - """A decorator to flush the input buffer prior to issuing a command. +import serial - Locks the comms channel so that a function must finish all its comms - before another can run. - """ - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - with self.comms_lock: - return func(self, *args, **kwargs) +import Pyro4 - return wrapper +from microscope import devices -class ObisLaser(LaserDevice): - """A class to control OBIS lasers from Coherent""" - def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): - super(ObisLaser, self).__init__() +@Pyro4.expose +class ObisLaser(devices.SerialDeviceMixIn, devices.LaserDevice): + def __init__(self, com, baud, timeout, *args, **kwargs): + super(ObisLaser, self).__init__(*args, **kwargs) self.connection = serial.Serial(port=com, baudrate=baud, timeout=timeout, @@ -47,78 +36,71 @@ def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE) # Start a logger. - response = self.send('SYSTem:INFormation:MODel?') - self._logger.info('OBIS laser model: [%s]' % response) - response = self.send('SYSTem:INFormation:SNUMber?') - self._logger.info('OBIS laser serial number: [%s]' % response) - response = self.send('SYSTem:CDRH?') - self._logger.info('CDRH safety: [%s]' % response) - response = self.send('SOURce:TEMPerature:APRobe?') - self._logger.info('TEC temperature control: [%s]' % response) - response = self.send('*TST?') - self._logger.info('Self test procedure: [%s]' % response) + self._write(b'SYSTem:INFormation:MODel?') + response = self._readline() + self._logger.info('OBIS laser model: [%s]' % response.decode()) + self._write(b'SYSTem:INFormation:SNUMber?') + response = self._readline() + self._logger.info('OBIS laser serial number: [%s]' % response.decode()) + self._write(b'SYSTem:CDRH?') + response = self._readline() + self._logger.info('CDRH safety: [%s]' % response.decode()) + self._write(b'SOURce:TEMPerature:APRobe?') + response = self._readline() + self._logger.info('TEC temperature control: [%s]' % response.decode()) + self._write(b'*TST?') + response = self._readline() + self._logger.info('Self test procedure: [%s]' % response.decode()) # We need to ensure that autostart is disabled so that we can switch emission # on/off remotely. - response = self.send('SYSTem:AUTostart?') + self._write(b'SYSTem:AUTostart?') + response = self._readline() self._logger.info('Response to Autostart: [%s]' % response) - self.comms_lock = threading.RLock() - - def send(self, command): - """Send command and retrieve response.""" - self._write(str(command)) - return self._readline() - - @lock_comms - def clear_fault(self): - self.send('SYSTem:ERRor:CLEar') - return self.get_status() - - def flush_buffer(self): - line = ' ' - while len(line) > 0: - line = self._readline() - - @lock_comms - def is_alive(self): - response1 = self.send('SOURce:AM:STATe?') - response2 = self.send('SOURce:TEMPerature:APRobe?') - return response1 and response2 == 'ON' - - @lock_comms + + def _write(self, command): + """Send a command.""" + response = self.connection.write(command + b'\r\n') + return response + + def _readline(self): + """Read a line from connection without leading and trailing whitespace. + We override from serialDeviceMixIn + """ + response = self.connection.readline().strip() + if self.connection.readline().strip() != b'OK': + print('Did not get a proper answer from the laser serial comm.') + return response + + def _flush_handshake(self): + self.connection.readline() + + @devices.SerialDeviceMixIn.lock_comms def get_status(self): result = [] - for cmd, stat in [('SOURce:AM:STATe?', 'Emission on?'), - ('SOURce:POWer:LEVel:IMMediate:AMPLitude?', 'Target power:'), - ('SOURce:POWer:LEVel?', 'Measured power:'), - ('SYSTem:STATus?', 'Status code?'), - ('SYSTem:FAULt?', 'Fault code?'), - ('SYSTem:HOURs?', 'Head operating hours:')]: - result.append(stat + ' ' + self.send(cmd)) + for cmd, stat in [(b'SOURce:AM:STATe?', 'Emission on?'), + (b'SOURce:POWer:LEVel:IMMediate:AMPLitude?', 'Target power:'), + (b'SOURce:POWer:LEVel?', 'Measured power:'), + (b'SYSTem:STATus?', 'Status code?'), + (b'SYSTem:FAULt?', 'Fault code?'), + (b'SYSTem:HOURs?', 'Head operating hours:')]: + self._write(cmd) + result.append(stat + ' ' + self._readline().decode()) return result - @lock_comms - def _on_shutdown(self): - # Disable laser. - self.disable() - - @lock_comms - def initialize(self): - """Initialization to do when cockpit connects.""" - self.flush_buffer() - # We don't want 'direct control' mode. - self.send('SOURce:AM:EXTernal DIGital') # Change to MIXED when analogue output is available - - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def enable(self): """Turn the laser ON. Return True if we succeeded, False otherwise.""" self._logger.info('Turning laser ON.') # Exiting Sleep Mode. - self.send('SOURce:TEMPerature:APRobe ON') + self._write(b'SOURce:TEMPerature:APRobe ON') + self._flush_handshake() # Turn on emission. - self.send('SOURce:AM:STATe ON') - response = self.send('SOURce:AM:STATe?') - self._logger.info("SOURce:AM:STATe? [%s]" % response) + self._write(b'SOURce:AM:STATe ON') + self._flush_handshake() + self._write(b'SOURce:AM:STATe?') + response = self._readline() + self._logger.info("SOURce:AM:STATe? [%s]" % response.decode()) if not self.get_is_on(): # Something went wrong. @@ -127,14 +109,37 @@ def enable(self): return False return True - @lock_comms + def _on_shutdown(self): + self.disable() + # We set the power to a safe level + self._set_power_mw(2) + # We want it back into direct control mode. + self._write(b'SOURce:AM:INTernal CWP') + self._flush_handshake() + + # Going into Sleep mode + self._write(b'SOURce:TEMPerature:APRobe OFF') + self._flush_handshake() + + + def initialize(self): + """Initialization to do when cockpit connects.""" + # self.flush_buffer() + # We ensure that handshaking is off. + self._write(b'SYSTem:COMMunicate:HANDshaking ON') + self._flush_handshake() + # We don't want 'direct control' mode. + # TODO: Change to MIXED when analogue output is available + self._write(b'SOURce:AM:EXTernal DIGital') + self._flush_handshake() + + @devices.SerialDeviceMixIn.lock_comms def disable(self): """Turn the laser OFF. Return True if we succeeded, False otherwise.""" self._logger.info('Turning laser OFF.') # Turning LASER OFF - self.send('SOURce:AM:STATe OFF') - # Going into Sleep mode - self.send('SOURce:TEMPerature:APRobe OFF') + self._write(b'SOURce:AM:STATe OFF') + self._flush_handshake() if self.get_is_on(): # Something went wrong. @@ -143,33 +148,49 @@ def disable(self): return False return True - @lock_comms + @devices.SerialDeviceMixIn.lock_comms + def isAlive(self): + return self.get_is_on + + @devices.SerialDeviceMixIn.lock_comms def get_is_on(self): """Return True if the laser is currently able to produce light.""" - response = self.send('SOURce:AM:STATe?') - return response == 'ON' - - @lock_comms + self._write(b'SOURce:AM:STATe?') + response = self._readline() + self._logger.info("Are we on? [%s]", response.decode()) + return response == b'ON' + + @devices.SerialDeviceMixIn.lock_comms + def _set_power(self, power_w): + """Sets the power level in Watts""" + if power_w > (self.get_max_power_mw() / 1000): + return + self._logger.info("Setting laser power to %.7sW", power_w) + self._write(b'SOURce:POWer:LEVel:IMMediate:AMPLitude %.5f' % power_w) + self._flush_handshake() + curr_power = self._get_power() + self._logger.info("Power response [%s]", curr_power) + return curr_power + + @devices.SerialDeviceMixIn.lock_comms def get_max_power_mw(self): """Gets the maximum laser power in mW.""" - power_w = self.send('SYSTem:INFormation:POWer?') - return float(power_w * 1000) + self._write(b'SYSTem:INFormation:POWer?') + power_w = self._readline() + return int(float(power_w.decode()) * 1000) - @lock_comms - def get_power_mw(self): - """Gets current power level in mW""" + @devices.SerialDeviceMixIn.lock_comms + def _get_power(self): if not self.get_is_on(): - return 0.0 - power_w = self.send('SOURce:POWer:LEVel?') - return 1000 * float(power_w) - - @lock_comms - def _set_power_mw(self, mW): - mW = min(mW, self.get_max_power_mw) - self._logger.info("Setting laser power to %.4fW." % (mW / 1000.0)) - self.send("SOURce:POWer:LEVel:IMMediate:AMPLitude %.4f" % mW / 1000.0) - - @lock_comms - def get_set_power_mw(self): - power_w = self.send('SOURce:POWer:LEVel:IMMediate:AMPLitude?') - return 1000 * float(power_w) + # Laser is not on. + return 0 + self._write(b'SOURce:POWer:LEVel?') + response = self._readline() + return float(response.decode()) + + def get_power_mw(self): + return 1000 * self._get_power() + + def _set_power_mw(self, mw): + mw = min(mw, self.get_max_power_mw()) + return self._set_power(mw / 1000) From ede06d9d622b167720a8182d2f83808268cc46c8 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Wed, 25 Jul 2018 16:36:35 +0200 Subject: [PATCH 17/27] Merged with Micron --- microscope/testsuite/devices.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microscope/testsuite/devices.py b/microscope/testsuite/devices.py index 39497d73..a64e3164 100644 --- a/microscope/testsuite/devices.py +++ b/microscope/testsuite/devices.py @@ -191,7 +191,7 @@ def get_status(self): result = [self._emission, self._power, self._set_point] return result - def enable(self): + def _on_enable(self): self._emission = True return self._emission @@ -201,7 +201,7 @@ def _on_shutdown(self): def initialize(self): pass - def disable(self): + def _on_disable(self): self._emission = False return self._emission @@ -297,7 +297,7 @@ def WriteDigital(self, value): self._logger.info('WriteDigital: %s' % "{0:b}".format(value)) self._digi = value - def MoveAbsoluteADU(self, aline, pos): + def MoveAbsolute(self, aline, pos): self._logger.info('MoveAbsoluteADU: line %d, value %d' % (aline, pos)) self._ana[aline] = pos From bc1adbebede701b3955cac087f8c9dd28fc283e8 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Tue, 31 Jul 2018 13:48:38 +0200 Subject: [PATCH 18/27] There is a timing issue in waiting for the response of the Deepstar. Adding a sleep of 0.2 sec solves the problem. Maybe there is another more elegant solution --- microscope/lasers/deepstar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/microscope/lasers/deepstar.py b/microscope/lasers/deepstar.py index e904f744..92f9ced2 100644 --- a/microscope/lasers/deepstar.py +++ b/microscope/lasers/deepstar.py @@ -18,6 +18,7 @@ # along with this program. If not, see . import serial +import time import Pyro4 @@ -45,6 +46,7 @@ def _write(self, command): # towards the byte limit, hence 14 (16-2) command = command.ljust(14) + b'\r\n' response = self.connection.write(command) + time.sleep(.2) return response @@ -119,11 +121,11 @@ def _set_power(self, level): if (level > 1.0) : return self._logger.info("level=%d", level) - power=int (level*0xFFF) + power = int(level*0xFFF) self._logger.info("power=%d", power) strPower = "PP%03X" % power self._logger.info("power level=%s", strPower) - self._write(six.b(strPower)) + self._write(strPower.encode()) response = self._readline() self._logger.info("Power response [%s]", response.decode()) return response From 293c9b3435631d31ceff82a2af858b05e84a99ed Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 12:01:58 +0200 Subject: [PATCH 19/27] Zyla working - settings isReadOnly was not called correctly when implemented as a lambda --- NEWS | 22 +- doc/contributors.rst | 1 + doc/supported-devices.rst | 9 +- microscope/cameras/SDK3.py | 23 +- microscope/cameras/SDK3Cam.py | 14 +- microscope/cameras/andorsdk3.py | 3 - microscope/clients.py | 12 +- microscope/devices.py | 333 +++++++--------------- microscope/deviceserver.py | 9 +- microscope/lasers/cobolt.py | 124 ++++---- microscope/lasers/deepstar.py | 119 ++++---- microscope/lasers/sapphire.py | 182 ++++++------ microscope/mirror/alpao.py | 2 + microscope/mirror/bmc.py | 3 + microscope/testsuite/devices.py | 167 ++++++++++- microscope/testsuite/deviceserver_test.py | 2 +- microscope/testsuite/test_devices.py | 127 +++++++++ setup.py | 69 +++-- 18 files changed, 696 insertions(+), 525 deletions(-) diff --git a/NEWS b/NEWS index 94974675..b2424dee 100644 --- a/NEWS +++ b/NEWS @@ -1,29 +1,35 @@ The following is a summary of the user-visible changes for each of python-microscope releases. -Upcoming release ----------------- +Version 0.2.0 (2018/06/13) +-------------------------- -* New device classes: +* New classes: * DeformableMirror - -* New MixIn classes: - * TriggerTargetMixIn - -* Added Enum for `TriggerType` and `TriggerMode`. + * SerialDeviceMixIn + * TriggerType + * TriggerMode * New hardware supported: * Alpao deformable mirrors * Boston Micromachines Corporation (BMC) deformable mirrors + * Thorlabs filter wheels + +* Abstract class for FilterWheel moved to the `microscope.devices` + module, where all other abstract device class are. * New module `microscope.gui` for simple testing of individual devices. * Now dependent on the enum34 package for python pre 3.4. +* Multiple fixes to support Python 3. + +* This is the last release with planned support for Python 2. + Version 0.1.0 (2017/05/04) -------------------------- diff --git a/doc/contributors.rst b/doc/contributors.rst index aeca18ac..64092b4b 100644 --- a/doc/contributors.rst +++ b/doc/contributors.rst @@ -16,3 +16,4 @@ Microscope: - Amy Howard - David Miguel Susano Pinto - Mick Phillips +- Tiago Susano Pinto diff --git a/doc/supported-devices.rst b/doc/supported-devices.rst index 32830b04..ee390fe8 100644 --- a/doc/supported-devices.rst +++ b/doc/supported-devices.rst @@ -22,9 +22,14 @@ Deformable Mirrors - Alpao (:class:`microscope.mirror.alpao`) - Boston Micromachines Corporation (:class:`microscope.mirror.bmc`) +Filter Wheels +------------- + +- Thorlabs (:class:`microscope.filterwheels.thorlabs`) + Lasers ------ - Cobolt (:class:`microscope.lasers.cobolt`) -- Deepstar (:class:`microscope.lasers.deepstar`) -- Sapphire (:class:`microscope.lasers.saphhire`) +- Coherent Sapphire (:class:`microscope.lasers.saphhire`) +- Omicron Deepstar (:class:`microscope.lasers.deepstar`) diff --git a/microscope/cameras/SDK3.py b/microscope/cameras/SDK3.py index 89753eea..f85a2a4e 100644 --- a/microscope/cameras/SDK3.py +++ b/microscope/cameras/SDK3.py @@ -21,9 +21,11 @@ ################## import ctypes -import os +import platform from ctypes import POINTER, c_int, c_uint, c_double, c_void_p +arch, plat = platform.architecture() + #### typedefs AT_H = ctypes.c_int AT_BOOL = ctypes.c_int @@ -33,13 +35,17 @@ _stdcall_libraries = {} -if os.name in ('nt', 'ce'): - _stdcall_libraries['ATCORE'] = ctypes.WinDLL('atcore') - _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('atutility') +if plat.startswith('Windows'): + if arch == '32bit': + _stdcall_libraries['ATCORE'] = ctypes.WinDLL('atcore') + _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('atutility') + else: + _stdcall_libraries['ATCORE'] = ctypes.OleDLL('atcore') + _stdcall_libraries['ATUTIL'] = ctypes.OleDLL('atutility') CALLBACKTYPE = ctypes.WINFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p) else: - _stdcall_libraries['ATCORE'] = ctypes.CDLL('atcore.so') - _stdcall_libraries['ATUTIL'] = ctypes.CDLL('atutility.so') + _stdcall_libraries['ATCORE'] = ctypes.CDLL('libatcore.so') + _stdcall_libraries['ATUTIL'] = ctypes.CDLL('libatutility.so') CALLBACKTYPE = ctypes.CFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p) #### Defines @@ -92,6 +98,7 @@ def errCode(name,value): errCode('AT_ERR_NULL_WAIT_PTR', 35) errCode('AT_ERR_NULL_PTRSIZE', 36) errCode('AT_ERR_NOMEMORY', 37) +errCode('AT_ERR_DEVICEINUSE', 38) errCode('AT_ERR_HARDWARE_OVERFLOW', 100) @@ -202,10 +209,8 @@ def __call__(self, *args): ars.append(ar) ret.append(r) #print r, r._type_ - - #print ars + res = self.f(*ars) - #print res if not res == AT_SUCCESS: if res == AT_ERR_TIMEDOUT or res == AT_ERR_NODATA: diff --git a/microscope/cameras/SDK3Cam.py b/microscope/cameras/SDK3Cam.py index e2f97764..878f9bb8 100644 --- a/microscope/cameras/SDK3Cam.py +++ b/microscope/cameras/SDK3Cam.py @@ -22,7 +22,7 @@ ################ -from .import SDK3 +from . import SDK3 class ATProperty(object): def connect(self, handle, propertyName): @@ -157,14 +157,14 @@ def connectProperties(self): for name, var in self.__dict__.items(): if isinstance(var, ATProperty): var.connect(self.handle, name) - - + + def shutdown(self): SDK3.Close(self.handle) #camReg.unregCamera() - - - - + + + + diff --git a/microscope/cameras/andorsdk3.py b/microscope/cameras/andorsdk3.py index dad1d120..bb4cb2e9 100644 --- a/microscope/cameras/andorsdk3.py +++ b/microscope/cameras/andorsdk3.py @@ -27,7 +27,6 @@ import time from six.moves import queue - from .SDK3Cam import * # SDK data pointer type @@ -103,8 +102,6 @@ def __init__(self, *args, **kwargs): SDK3.InitialiseLibrary() self._index = kwargs.get('index', 0) self.handle = None - #self._sdk3cam = SDK3Camera(self._index) - #SDK3Camera.__init__(self, self._index) self.add_setting('use_callback', 'bool', lambda: self._using_callback, self._enable_callback, diff --git a/microscope/clients.py b/microscope/clients.py index 34ee38bd..cbe5db64 100644 --- a/microscope/clients.py +++ b/microscope/clients.py @@ -18,6 +18,7 @@ """TODO: complete this docstring """ import inspect +import itertools import Pyro4 import socket import threading @@ -43,11 +44,16 @@ def _connect(self): """Connect to a proxy and set up self passthrough to proxy methods.""" self._proxy = Pyro4.Proxy(self._url) self._proxy._pyroGetMetadata() - # Derived classes my over-ride some methods. Leave these alone. + + # Derived classes may over-ride some methods. Leave these alone. my_methods = [m[0] for m in inspect.getmembers(self, predicate=inspect.ismethod)] methods = set(self._proxy._pyroMethods).difference(my_methods) - for method in methods: - setattr(self, method, getattr(self._proxy, method)) + # But in the case of propertyes, we need to inspect the class. + my_properties = [m[0] for m in inspect.getmembers(self.__class__, predicate=inspect.isdatadescriptor)] + properties = set(self._proxy._pyroAttrs).difference(my_properties) + + for attr in itertools.chain(methods, properties): + setattr(self, attr, getattr(self._proxy, attr)) class DataClient(Client): diff --git a/microscope/devices.py b/microscope/devices.py index d335521f..d0f5132b 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -25,6 +25,7 @@ """ import abc +import functools import itertools import logging import time @@ -32,6 +33,7 @@ from collections import OrderedDict from six import string_types from threading import Thread +import threading import Pyro4 import numpy @@ -46,7 +48,7 @@ (TRIGGER_AFTER, TRIGGER_BEFORE, TRIGGER_DURATION, TRIGGER_SOFT) = range(4) # Device types. -(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER, UWAVEFRONTSENSOR) = range(7) +(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER) = range(6) # Mapping of setting data types to descriptors allowed-value description types. # For python 2 and 3 compatibility, we convert the type into a descriptor string. @@ -241,6 +243,7 @@ def set_setting(self, name, value): except Exception as err: self._logger.error("in set_setting(%s):" % (name), exc_info=err) + @Pyro4.expose def describe_setting(self, name): """Return ordered setting descriptions as a list of dicts.""" @@ -289,9 +292,17 @@ def update_settings(self, incoming, init=False): results[key] = NotImplemented update_keys.remove(key) continue - if self.settings[key]['readonly']: - continue - self.settings[key]['set'](incoming[key]) + if callable(self.settings[key]['readonly']): + if self.settings[key]['readonly'](): + continue + else: + self.settings[key]['set'](incoming[key]) + else: + if self.settings[key]['readonly']: + continue + else: + self.settings[key]['set'](incoming[key]) + # Read back values in second loop. for key in update_keys: results[key] = self.settings[key]['get']() @@ -743,6 +754,59 @@ def set_trigger(self, ttype, tmode): pass +@Pyro4.expose +class SerialDeviceMixIn(object): + """MixIn for devices that are controlled via serial. + + Currently handles the flushing and locking of the comms channel + until a command has finished, and the passthrough to the serial + channel. + + TODO: add more logic to handle the code duplication of serial + devices. + """ + def __init__(self, *args, **kwargs): + super(SerialDeviceMixIn, self).__init__(*args, **kwargs) + ## TODO: We should probably construct the connection here but + ## the Serial constructor takes a lot of arguments, and + ## it becomes tricky to separate those from arguments to + ## the constructor of other parent classes. + self.connection = None # serial.Serial (to be constructed by child) + self._comms_lock = threading.RLock() + + def _readline(self): + """Read a line from connection without leading and trailing whitespace. + """ + return self.connection.readline().strip() + + def _write(self, command): + """Send a command to the device. + + This is not a simple passthrough to ``serial.Serial.write``, + it will append ``b'\\r\\n'`` to command. Override this method + if a device requires a specific format. + """ + return self.connection.write(command + b'\r\n') + + @staticmethod + def lock_comms(func): + """Decorator to flush input buffer and lock communications. + + There have been problems with the DeepStar lasers returning + junk characters after the expected response, so it is + advisable to flush the input buffer prior to running a command + and subsequent readline. It also locks the comms channel so + that a function must finish all its communications before + another can run. + """ + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + with self._comms_lock: + self.connection.flushInput() + return func(self, *args, **kwargs) + return wrapper + + @Pyro4.expose class DeformableMirror(Device): """Base class for Deformable Mirrors. @@ -848,23 +912,8 @@ class LaserDevice(Device): @abc.abstractmethod def __init__(self, *args, **kwargs): super(LaserDevice, self).__init__(*args, **kwargs) - self.connection = None self._set_point = None - def _read(self, num_chars): - """Simple passthrough to read numChars from connection.""" - return self.connection.read(num_chars) - - def _readline(self): - """Simple passthrough to read one line from connection.""" - return self.connection.readline().strip() - - def _write(self, command): - """Send a command to the device.""" - # Override if a specific format is required. - response = self.connection.write(command + '\r\n') - return response - @abc.abstractmethod def get_status(self): """Query and return the laser status.""" @@ -899,226 +948,50 @@ def _set_power_mw(self, mw): @Pyro4.expose def set_power_mw(self, mw): - """Set the power from an argument in mW and save the set point.""" - self._set_point = mw - self._set_power_mw(mw) - - -# === WavefrontSensorDevice === -class WavefrontSensorDevice(CameraDevice): - __metaclass__ = abc.ABCMeta - """Adds functionality to DataDevice to support WavefrontSensors (WFS). - As, in practice, all wavefront sensors are based on a camera, - I think it makes sense here to subclassing a CameraDevice - - Defines the interface for WFSs. - Applies transformations to the acquired data to get the relevant information: - intensity and wavefront maps, Zernike polynomials,... - """ - def __init__(self, *args, **kwargs): - super(WavefrontSensorDevice, self).__init__(**kwargs) - # Add WFS settings - # list of Zernike indexes to be returned - self._zernike_indexes = [] - # Filter the phase map returned by a number of the Zernike measurements - self._zernike_phase_filter = [] - # WaveLength at which the phase map is calculated - self._wavelength_nm = None - self._pupil_shape = 4 # Defaults to circular shape - self._pupil_edge = 0 # Defaults to no edge - self.add_setting('wavelength_nm', 'float', - self._get_wavelength_nm, - self._set_wavelength_nm, - (300.0, 2000,0)) - self.add_setting('pupil_shape', 'int', - lambda: self._pupil_shape, - None, - (3, 4), - readonly=True) - self.add_setting('pupil_edge', 'int', - lambda: self._pupil_edge, - None, - (0, 100), - readonly=True) - - # Some acquisition related methods # - - @abc.abstractmethod - def _process_data(self, data): - """Apply necessary transformations to data to be served to the client. - - Return as a dictionary: - - phase_map: a 2D array containing the phase - - intensity_map: a 2D array containing the intensity - - linearity: some measure of the linearity of the data. - Simple saturation at the intensity map might not be enough to indicate - if we are exposing correctly to get a accurate measure of the phase. - - tilts: a tuple containing X and Y tilts - - RMS: the root mean square measurement - - PtoV: peak to valley measurement - - zernike_polynomials: a list with the relevant Zernike polynomials - """ - return data - - # Some measurements related methods - # We made the distinction here between ROI (form CameraDevice superclass) - # and pupil. - # ROI is the area that is acquired and shown in the intensity images. - # The pupil is the area that is analyzed for measuring phase. - - @abc.abstractmethod - def _get_pupil(self): - """Return the pupil that is measured. - Pupil on WFS are not per se rectangular neither circular. - We might define them as: - 1- a mask: a binary 2D array of the size of the sensor - 2- as a list of sub-pupils each containing: - - the size (left, top, width, height) of a surrounding or enclosed rectangle - - whether the rectangle is surrounding or enclosed - - the contained shape: circular/oval or rectangular - While in most cases the pupil can be defined by a simple circle, it is - convenient to add the option of a mask. - Pupil has to be returned as a numpy array with the same orientation as the ROI - as it is going to use the same transform methods - """ - pass - - @Pyro4.expose - def get_pupil(self): - """Return pupil in a convenient way for the client.""" - pupil = self._get_pupil() - # TODO: depending on the format of the pupil, we might have to transform it - # if self._transform[2]: - # # 90 degree rotation - # pupil = numpy.rot90(pupil) - return pupil + """Set the power from an argument in mW and save the set point. - @abc.abstractmethod - def _set_pupil(self, shape=None, edge=None): - """Set the pupil on the hardware, return True if successful. - If type and edge are not specified, they should not change and follow the main ROI.""" - return False - - @Pyro4.expose - def set_pupil(self, shape=None, edge=None): - """Set the pupil referenced to the main ROI. - - :param shape: this is defining the shape of the pupil. - 3 = Rectangle, 4 = Oval or Circle - :param edge: distance in pixels from the edge of the main ROI - Return True if pupil set correctly, False otherwise.""" - # TODO: depending on the format of the pupil, we might have to transform it - # if self._transform[2]: - # roi = (top, left, height, width) - # else: - # roi = (left, top, width, height) - if not edge: - edge = self._pupil_edge - else: - self._pupil_edge = edge - if not shape: - shape = self._pupil_shape - else: - self._pupil_shape = shape + Args: + mw (float): Power in mW. Value will be clipped to the + valid range for the laser. See the methods + :func:`get_max_power_mw` and :func:`get_min_power_mw` + to retrieve the valid range. - return self._set_pupil(shape=shape, edge=edge) - - @abc.abstractmethod - def _set_reference(self, source): - """Reference the WFS to either: - - the manufacturer's reference - - a reference from a grabbed measurement - - a reference from a file - - :return: True if successful + Returns: + void """ - pass - - @Pyro4.expose - def set_reference(self, source): - """Reference the WFS to either: - - the manufacturer's reference - - a reference from a grabbed measurement - - a reference from a file - for all the phase measurements - """ - self._set_reference(source) - - @abc.abstractmethod - def _get_reference(self): - """Get the reference of the WFS""" - pass - - @Pyro4.expose - def get_reference(self): - """Return to what the WFS is currently referenced to.""" - return self._get_reference() - - @Pyro4.expose - @abc.abstractmethod - def acquire_dark_image(self): - """Acquire a dark image on the camera""" - pass - - @Pyro4.expose - def get_zernike_indexes(self): - """Returns a list with the Zernike indexes that are returned in - a measurement""" - return self._zernike_indexes - - @abc.abstractmethod - def _set_zernike_indexes(self, indexes): - """Set the indexes of the polynomials that have to be returned by the SDK or the hardware. + self._set_point = mw + self._set_power_mw(mw) - :param indexes: a list of integers representing the indexes to return - :return True if success - """ - pass - @Pyro4.expose - def set_zernike_indexes(self, indexes): - """Set the indexes of the Zernike polynomials - that have to be returned by the WFS""" - if self._set_zernike_indexes(indexes): - self._zernike_indexes = indexes +class FilterWheelBase(Device): + __metaclass__ = abc.ABCMeta - @Pyro4.expose - def get_zernike_phase_filter(self): - return self._zernike_phase_filter + def __init__(self, filters, *args, **kwargs): + super(FilterWheelBase, self).__init__(*args, **kwargs) + self._utype = UFILTER + self._filters = dict(map(lambda f: (f[0], f[1:]), filters)) + self._inv_filters = {val: key for key, val in self._filters.items()} + # The position as an integer. + self.add_setting('position', + 'int', + self._get_position, + self._set_position, + (0, 5)) + # The selected filter. + self.add_setting('filter', + 'enum', + lambda: self._filters[self._get_position()], + lambda val: self._set_position(self._inv_filters[val]), + self._filters.values) @abc.abstractmethod - def _set_zernike_phase_filter(self, phase_filter): - """Set a filter of Zernike polynomials by which the phase map has to be filtered. - - :param phase_filter: a list of integers representing the indexes to filter. [] no filtering - :return True if success - """ - pass - - @Pyro4.expose - def set_zernike_phase_filter(self, phase_filter): - """Set a filter of Zernike polynomials by which the phase map has to be filtered. - - :param phase_filter: a list of integers representing the indexes to filter. [] no filtering - """ - if self._set_zernike_phase_filter(phase_filter): - self._zernike_phase_filter = phase_filter - - @Pyro4.expose - def get_wavelength_nm(self): - return self._wavelength_nm + def _get_position(self): + return self._position @abc.abstractmethod - def _set_wavelength_nm(self, wavelength): - """Sets the wavelength that is used for tha phase calculation - - :param wavelength: integer representing the wavelength in nm. - :return True if success - """ - pass + def _set_position(self, position): + self._position = position @Pyro4.expose - def set_wavelength_nm(self, wavelength): - if self._set_wavelength_nm(wavelength): - self._wavelength_nm = wavelength - + def get_filters(self): + return self._filters.items() diff --git a/microscope/deviceserver.py b/microscope/deviceserver.py index 2f4d68f7..1387e5fb 100644 --- a/microscope/deviceserver.py +++ b/microscope/deviceserver.py @@ -157,13 +157,11 @@ def run(self): # Run the Pyro daemon in a separate thread so that we can do # clean shutdown under Windows. - pyro_thread = Thread(target=Pyro4.Daemon.serveSimple, - args=({self._device: - type(self._device).__name__},), - kwargs={'daemon': pyro_daemon, 'ns': - False}) + pyro_daemon.register(self._device, type(self._device).__name__) + pyro_thread = Thread(target = pyro_daemon.requestLoop) pyro_thread.daemon = True pyro_thread.start() + logger.info('Serving %s' % pyro_daemon.uriFor(self._device)) # Wait for termination event. We should just be able to call # wait() on the exit_event, but this causes issues with locks # in multiprocessing - see http://bugs.python.org/issue30975 . @@ -247,7 +245,6 @@ def keep_alive(): while not exit_event.is_set(): for s in servers: if s.is_alive(): - logger.info("%s is alive." % s.pid) continue else: logger.info(("DeviceServer Failure. Process %s is dead with" diff --git a/microscope/lasers/cobolt.py b/microscope/lasers/cobolt.py index 63cb21c2..8ecf9f8a 100644 --- a/microscope/lasers/cobolt.py +++ b/microscope/lasers/cobolt.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 # # Copyright 2016 Mick Phillips (mick.phillips@gmail.com) +# Copyright 2018 David Pinto # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,102 +17,85 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import serial -import threading -import time -from microscope import devices -import functools - - -def lock_comms(func): - """A decorator to flush the input buffer prior to issuing a command. - Locks the comms channel so that a function must finish all its comms - before another can run. - """ - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - with self.comms_lock: - return func(self, *args, **kwargs) +import Pyro4 - return wrapper +from microscope import devices -class CoboltLaser(devices.LaserDevice): - def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): - super(CoboltLaser, self).__init__() +@Pyro4.expose +class CoboltLaser(devices.SerialDeviceMixIn, devices.LaserDevice): + def __init__(self, com=None, baud=None, timeout=0.01, *args, **kwargs): + super(CoboltLaser, self).__init__(*args, **kwargs) self.connection = serial.Serial(port = com, baudrate = baud, timeout = timeout, stopbits = serial.STOPBITS_ONE, bytesize = serial.EIGHTBITS, parity = serial.PARITY_NONE) # Start a logger. - self._write('sn?') - response = self._readline() - self._logger.info("Cobolt laser serial number: [%s]" % response) + response = self.send(b'sn?') + self._logger.info("Cobolt laser serial number: [%s]", response.decode()) # We need to ensure that autostart is disabled so that we can switch emission # on/off remotely. - self._write('@cobas 0') - self._logger.info("Response to @cobas 0 [%s]" % self._readline()) - self.comms_lock = threading.RLock() + response = self.send(b'@cobas 0') + self._logger.info("Response to @cobas 0 [%s]", response.decode()) def send(self, command): """Send command and retrieve response.""" - self._write(str(command)) + self._write(command) return self._readline() - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def clearFault(self): - self._write('cf') - self._readline() + self.send(b'cf') return self.get_status() def flush_buffer(self): - line = ' ' + line = b' ' while len(line) > 0: line = self._readline() - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def is_alive(self): - self._write('l?') - response = self._readline() - return response in '01' + response = self.send(b'l?') + return response in b'01' - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_status(self): result = [] - for cmd, stat in [('l?', 'Emission on?'), - ('p?', 'Target power:'), - ('pa?', 'Measured power:'), - ('f?', 'Fault?'), - ('hrs?', 'Head operating hours:')]: - self._write(cmd) - result.append(stat + ' ' + self._readline()) + for cmd, stat in [(b'l?', 'Emission on?'), + (b'p?', 'Target power:'), + (b'pa?', 'Measured power:'), + (b'f?', 'Fault?'), + (b'hrs?', 'Head operating hours:')]: + response = self.send(cmd) + result.append(stat + " " + response.decode()) return result - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def _on_shutdown(self): # Disable laser. - self.send('l0') - self.send('@cob0') + self.disable() + self.send(b'@cob0') self.flush_buffer() ## Initialization to do when cockpit connects. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def initialize(self): self.flush_buffer() #We don't want 'direct control' mode. - self.send('@cobasdr 0') + self.send(b'@cobasdr 0') # Force laser into autostart mode. - self.send('@cob1') + self.send(b'@cob1') ## Turn the laser ON. Return True if we succeeded, False otherwise. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def enable(self): self._logger.info("Turning laser ON.") # Turn on emission. - response = self.send('l1') - self._logger.info("l1: [%s]" % response) + response = self.send(b'l1') + self._logger.info("l1: [%s]", response.decode()) if not self.get_is_on(): # Something went wrong. @@ -122,45 +106,43 @@ def enable(self): ## Turn the laser OFF. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def disable(self): self._logger.info("Turning laser OFF.") - self._write('l0') - return self._readline() + return self.send(b'l0').decode() ## Return True if the laser is currently able to produce light. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_is_on(self): - self._write('l?') - response = self._readline() - return response == '1' + response = self.send(b'l?') + return response == b'1' - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_max_power_mw(self): # 'gmlp?' gets the maximum laser power in mW. - self._write('gmlp?') - response = self._readline() + response = self.send(b'gmlp?') return float(response) - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_power_mw(self): if not self.get_is_on(): return 0 - self._write('pa?') - return 1000 * float(self._readline()) + response = self.send(b'pa?') + return 1000 * float(response) - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def _set_power_mw(self, mW): - mW = min(mW, self.get_max_power_mw) - self._logger.info("Setting laser power to %.4fW." % (mW / 1000.0, )) - return self.send("@cobasp %.4f" % (mW / 1000.0)) + mW = min(mW, self.get_max_power_mw()) + W_str = '%.4f' % (mW / 1000.0) + self._logger.info("Setting laser power to %s W.", W_str) + return self.send(b'@cobasp ' + W_str.encode()) - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_set_power_mw(self): - self._write('p?') - return 1000 * float(self._readline()) + response = self.send(b'p?') + return 1000 * float(response) diff --git a/microscope/lasers/deepstar.py b/microscope/lasers/deepstar.py index 4531b079..6a9a8815 100644 --- a/microscope/lasers/deepstar.py +++ b/microscope/lasers/deepstar.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 # # Copyright 2016 Mick Phillips (mick.phillips@gmail.com) +# Copyright 2018 David Pinto # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,90 +17,68 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import serial -import threading -import functools -from microscope import devices - -def _flush_buffer(func): - """A decorator to flush the input buffer prior to issuing a command. +import Pyro4 - There have been problems with the DeepStar lasers returning junk characters - after the expected response, so it is advisable to flush the input buffer - prior to running a command and subsequent readline. It also locks the comms - channel so that a function must finish all its comms before another can run. - """ - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - with self.comms_lock: - self.connection.flushInput() - return func(self, *args, **kwargs) - - return wrapper +from microscope import devices -class DeepstarLaser(devices.LaserDevice): - def __init__(self, com, baud, timeout, **kwargs): - super(DeepstarLaser, self).__init__() +@Pyro4.expose +class DeepstarLaser(devices.SerialDeviceMixIn, devices.LaserDevice): + def __init__(self, com, baud, timeout, *args, **kwargs): + super(DeepstarLaser, self).__init__(*args, **kwargs) self.connection = serial.Serial(port = com, baudrate = baud, timeout = timeout, stopbits = serial.STOPBITS_ONE, bytesize = serial.EIGHTBITS, parity = serial.PARITY_NONE) # If the laser is currently on, then we need to use 7-byte mode; otherwise we need to # use 16-byte mode. - self._write('S?') + self._write(b'S?') response = self._readline() - self._logger.info("Current laser state: [%s]" % response) - self.comms_lock = threading.RLock() - + self._logger.info("Current laser state: [%s]", response.decode()) def _write(self, command): """Send a command.""" - # We'll need to pad the command out to 16 bytes. There's also a 7-byte mode but - # we never need to use it. - commandLength = 16 - # CR/LF count towards the byte limit, hence the -2. - command = command + (' ' * (commandLength - 2 - len(command))) - response = self.connection.write(command + '\r\n') + # We'll need to pad the command out to 16 bytes. There's also + # a 7-byte mode but we never need to use it. CR/LF counts + # towards the byte limit, hence 14 (16-2) + command = command.ljust(14) + b'\r\n' + response = self.connection.write(command) return response ## Get the status of the laser, by sending the # STAT0, STAT1, STAT2, and STAT3 commands. - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def get_status(self): result = [] - for i in xrange(4): - self._write('STAT%d' % i) - result.append(self._readline()) + for i in range(4): + self._write(('STAT%d' % i).encode()) + result.append(self._readline().decode()) return result ## Turn the laser ON. Return True if we succeeded, False otherwise. - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def enable(self): self._logger.info("Turning laser ON.") - self._write('LON') - response = self._readline() #Turn on deepstar mode with internal voltage ref - self._logger.info("Enable response: [%s]" % response) - self._write('L2') - response = self._readline() - self._logger.info("L2 response: [%s]" % response) #Enable internal peak power - self._write('IPO') - response = self._readline() - self._logger.info("Enable-internal peak power response: [%s]" % response) #Set MF turns off internal digital and bias modulation - self._write('MF') + for cmd, msg in [(b'LON', 'Enable response: [%s]'), + (b'L2', 'L2 response: [%s]'), + (b'IPO', 'Enable-internal peak power response: [%s]'), + (b'MF', 'MF response [%s]')]: + self._write(cmd) response = self._readline() - self._logger.info("MF response [%s]" % response) + self._logger.info(msg, response.decode()) if not self.get_is_on(): # Something went wrong. - self._write('S?') + self._write(b'S?') response = self._readline() - self._logger.error("Failed to turn on. Current status: %s" % response) + self._logger.error("Failed to turn on. Current status: [%s]", + response.decode()) return False return True @@ -110,61 +89,61 @@ def initialize(self): pass ## Turn the laser OFF. - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def disable(self): self._logger.info("Turning laser OFF.") - self._write('LF') - return self._readline() + self._write(b'LF') + return self._readline().decode() - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def isAlive(self): - self._write('S?') + self._write(b'S?') response = self._readline() - return response.startswith('S') + return response.startswith(b'S') ## Return True if the laser is currently able to produce light. We assume this is equivalent # to the laser being in S2 mode. - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def get_is_on(self): - self._write('S?') + self._write(b'S?') response = self._readline() - self._logger.info("Are we on? [%s]" % response) - return response == 'S2' + self._logger.info("Are we on? [%s]", response.decode()) + return response == b'S2' - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def _set_power(self, level): if (level > 1.0) : return - self._logger.info("level=%d" % level) + self._logger.info("level=%d", level) power=int (level*0xFFF) - self._logger.info("power=%d" % power) + self._logger.info("power=%d", power) strPower = "PP%03X" % power - self._logger.info("power level=%s" %strPower) - self._write(strPower) + self._logger.info("power level=%s", strPower) + self._write(six.b(strPower)) response = self._readline() - self._logger.info("Power response [%s]" % response) + self._logger.info("Power response [%s]", response.decode()) return response - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def get_max_power_mw(self): # Max power in mW is third token of STAT0. - self._write('STAT0') + self._write(b'STAT0') response = self._readline() return int(response.split()[2]) - @_flush_buffer + @devices.SerialDeviceMixIn.lock_comms def _get_power(self): if not self.get_is_on(): # Laser is not on. return 0 - self._write('PP?') + self._write(b'PP?') response = self._readline() - return int('0x' + response.strip('PP'), 16) + return int(b'0x' + response.strip(b'P'), 16) def get_power_mw(self): diff --git a/microscope/lasers/sapphire.py b/microscope/lasers/sapphire.py index 63cb21c2..4a67f864 100644 --- a/microscope/lasers/sapphire.py +++ b/microscope/lasers/sapphire.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 # # Copyright 2016 Mick Phillips (mick.phillips@gmail.com) +# and 2017 Ian Dobbie (Ian.Dobbie@gmail.com) +# and 2018 Tiago Susano Pinto (tiagosusanopinto@gmail.com) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,151 +18,163 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import serial -import threading -import time -from microscope import devices -import functools +import Pyro4 -def lock_comms(func): - """A decorator to flush the input buffer prior to issuing a command. +from microscope import devices - Locks the comms channel so that a function must finish all its comms - before another can run. - """ - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - with self.comms_lock: - return func(self, *args, **kwargs) - return wrapper +@Pyro4.expose +class SapphireLaser(devices.SerialDeviceMixIn, devices.LaserDevice): + laser_status = { + b'1': 'Start up', + b'2': 'Warmup', + b'3': 'Standby', + b'4': 'Laser on', + b'5': 'Laser ready', + b'6': 'Error', + } -class CoboltLaser(devices.LaserDevice): - def __init__(self, com=None, baud=None, timeout=0.01, **kwargs): - super(CoboltLaser, self).__init__() + def __init__(self, com=None, baud=19200, timeout=0.5, *args, **kwargs): + # laser controller must run at 19200 baud, 8+1 bits, + # no parity or flow control + # timeout is recomended to be over 0.5 + super(SapphireLaser, self).__init__(*args, **kwargs) self.connection = serial.Serial(port = com, baudrate = baud, timeout = timeout, stopbits = serial.STOPBITS_ONE, bytesize = serial.EIGHTBITS, parity = serial.PARITY_NONE) - # Start a logger. - self._write('sn?') - response = self._readline() - self._logger.info("Cobolt laser serial number: [%s]" % response) - # We need to ensure that autostart is disabled so that we can switch emission - # on/off remotely. - self._write('@cobas 0') - self._logger.info("Response to @cobas 0 [%s]" % self._readline()) - self.comms_lock = threading.RLock() + # Turning off command prompt + self.send(b'>=0') + # Head ID value is a float point value, + # but only the integer part is significant + headID = int(float(self.send(b'?hid'))) + self._logger.info("Sapphire laser serial number: [%s]" % headID) + + def _write(self, command): + count = super(SapphireLaser, self)._write(command) + ## This device always writes backs something. If echo is on, + ## it's the whole command, otherwise just an empty line. Read + ## it and throw it away. + self._readline() + return count def send(self, command): """Send command and retrieve response.""" - self._write(str(command)) + self._write(command) return self._readline() - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def clearFault(self): - self._write('cf') - self._readline() + self.flush_buffer() return self.get_status() def flush_buffer(self): - line = ' ' + line = b' ' while len(line) > 0: line = self._readline() - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def is_alive(self): - self._write('l?') - response = self._readline() - return response in '01' + return self.send(b'?l') in b'01' - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_status(self): result = [] - for cmd, stat in [('l?', 'Emission on?'), - ('p?', 'Target power:'), - ('pa?', 'Measured power:'), - ('f?', 'Fault?'), - ('hrs?', 'Head operating hours:')]: - self._write(cmd) - result.append(stat + ' ' + self._readline()) + + status_code = self.send(b'?sta') + result.append(('Laser status: ' + + self.laser_status.get(status_code, 'Undefined'))) + + for cmd, stat in [(b'?l', 'Ligh Emission on?'), + (b'?t', 'TEC Servo on?'), + (b'?k', 'Key Switch on?'), + (b'?sp', 'Target power:'), + (b'?p', 'Measured power:'), + (b'?hh', 'Head operating hours:')]: + result.append(stat + ' ' + self.send(cmd).decode()) + + self._write(b'?fl') + faults = self._readline() + response = self._readline() + while response: + faults += b' ' + response + response = self._readline() + + result.append(faults.decode()) return result - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def _on_shutdown(self): # Disable laser. - self.send('l0') - self.send('@cob0') + self._write(b'l=0') self.flush_buffer() ## Initialization to do when cockpit connects. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def initialize(self): self.flush_buffer() - #We don't want 'direct control' mode. - self.send('@cobasdr 0') - # Force laser into autostart mode. - self.send('@cob1') ## Turn the laser ON. Return True if we succeeded, False otherwise. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def enable(self): self._logger.info("Turning laser ON.") # Turn on emission. - response = self.send('l1') - self._logger.info("l1: [%s]" % response) + response = self.send(b'l=1') + self._logger.info("l=1: [%s]" % response.decode()) + + # Enabling laser might take more than 500ms (default timeout) + prevTimeout = self.connection.timeout + self.connection.timeout = max(1, prevTimeout) + isON = self.get_is_on() + self.connection.timeout = prevTimeout - if not self.get_is_on(): + if not isON: # Something went wrong. self._logger.error("Failed to turn on. Current status:\r\n") self._logger.error(self.get_status()) - return False - return True + return isON ## Turn the laser OFF. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def disable(self): self._logger.info("Turning laser OFF.") - self._write('l0') - return self._readline() + return self._write(b'l=0') ## Return True if the laser is currently able to produce light. - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_is_on(self): - self._write('l?') - response = self._readline() - return response == '1' + return self.send(b'?l') == b'1' - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_max_power_mw(self): - # 'gmlp?' gets the maximum laser power in mW. - self._write('gmlp?') - response = self._readline() - return float(response) + # '?maxlp' gets the maximum laser power in mW. + return float(self.send(b'?maxlp')) + @devices.SerialDeviceMixIn.lock_comms + def get_min_power_mw(self): + # '?minlp' gets the minimum laser power in mW. + return float(self.send(b'?minlp')) - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def get_power_mw(self): - if not self.get_is_on(): - return 0 - self._write('pa?') - return 1000 * float(self._readline()) - + return float(self.send(b'?p')) - @lock_comms + @devices.SerialDeviceMixIn.lock_comms def _set_power_mw(self, mW): - mW = min(mW, self.get_max_power_mw) - self._logger.info("Setting laser power to %.4fW." % (mW / 1000.0, )) - return self.send("@cobasp %.4f" % (mW / 1000.0)) - - - @lock_comms + mW = max(min(mW, self.get_max_power_mw()), self.get_min_power_mw()) + mW_str = '%.3f' % mW + self._logger.info("Setting laser power to %s mW." % mW_str) + # using send instead of _write, because + # if laser is not on, warning is returned + return self.send(b'p=%s' % mW_str.encode()) + + @devices.SerialDeviceMixIn.lock_comms def get_set_power_mw(self): - self._write('p?') - return 1000 * float(self._readline()) + return float(self.send('b?sp')) diff --git a/microscope/mirror/alpao.py b/microscope/mirror/alpao.py index 5f2124ac..a5476505 100644 --- a/microscope/mirror/alpao.py +++ b/microscope/mirror/alpao.py @@ -19,6 +19,7 @@ import ctypes import warnings +import Pyro4 import numpy import six @@ -30,6 +31,7 @@ import microscope._wrappers.asdk as asdk +@Pyro4.expose class AlpaoDeformableMirror(TriggerTargetMixIn, DeformableMirror): """Class for Alpao deformable mirror. diff --git a/microscope/mirror/bmc.py b/microscope/mirror/bmc.py index ef163cf8..49b5ff08 100644 --- a/microscope/mirror/bmc.py +++ b/microscope/mirror/bmc.py @@ -23,12 +23,15 @@ import os import warnings +import Pyro4 import six from microscope.devices import DeformableMirror import microscope._wrappers.BMC as BMC + +@Pyro4.expose class BMCDeformableMirror(DeformableMirror): def __init__(self, serial_number, *args, **kwargs): super(BMCDeformableMirror, self).__init__() diff --git a/microscope/testsuite/devices.py b/microscope/testsuite/devices.py index 0fdecbb6..1721daac 100644 --- a/microscope/testsuite/devices.py +++ b/microscope/testsuite/devices.py @@ -20,14 +20,16 @@ ## along with Microscope. If not, see . import random +import sys import time import Pyro4 import numpy as np +from PIL import Image, ImageFont, ImageDraw from microscope import devices from microscope.devices import keep_acquiring -from microscope.filterwheel import FilterWheelBase +from microscope.devices import FilterWheelBase @Pyro4.expose @Pyro4.behavior('single') @@ -47,7 +49,11 @@ def __init__(self, *args, **kwargs): lambda: (0, 100)) self._acquiring = False self._exposure_time = 0.1 - self._triggered = False + self._triggered = 0 + # Count number of images sent since last enable. + self._sent = 0 + # Font for rendering counter in images. + self._font = ImageFont.load_default() def _set_error_percent(self, value): self._error_percent = value @@ -64,18 +70,29 @@ def _create_buffers(self): #time.sleep(0.5) def _fetch_data(self): - if self._acquiring and self._triggered: + if self._acquiring and self._triggered > 0: if random.randint(0, 100) < self._error_percent: self._logger.info('Raising exception') raise Exception('Exception raised in TestCamera._fetch_data') self._logger.info('Sending image') time.sleep(self._exposure_time) - self._triggered = False - return np.random.random_integers(255, - size=(512,512)).astype(np.int16) + self._triggered -= 1 + # Create an image + size = (512,512) + image = Image.fromarray( + np.random.random_integers(255, size=size).astype(np.uint8), 'L') + # Render text + text = "%d" % self._sent + tsize = self._font.getsize(text) + ctx = ImageDraw.Draw(image) + ctx.rectangle([size[0]-tsize[0]-8, 0, size[0], tsize[1]+8], fill=0) + ctx.text((size[0]-tsize[0]-4, 4), text, fill=255) + + self._sent += 1 + return np.asarray(image).T def abort(self): - self._logger.info('Disabling acquisition.') + self._logger.info("Disabling acquisition; %d images sent." % self._sent) if self._acquiring: self._acquiring = False @@ -100,6 +117,7 @@ def _on_enable(self): self.abort() self._create_buffers() self._acquiring = True + self._sent = 0 self._logger.info("Acquisition enabled.") return True @@ -122,7 +140,7 @@ def soft_trigger(self): self._logger.info('Trigger received; self._acquiring is %s.' % self._acquiring) if self._acquiring: - self._triggered = True + self._triggered += 1 def _get_binning(self): return (1,1) @@ -141,6 +159,8 @@ def _set_roi(self, x, y, width, height): def _on_shutdown(self): pass + +@Pyro4.expose class TestFilterWheel(FilterWheelBase): def __init__(self, filters=[], *args, **kwargs): super(TestFilterWheel, self).__init__(filters, *args, **kwargs) @@ -170,7 +190,7 @@ def get_status(self): result = [self._emission, self._power, self._set_point] return result - def enable(self): + def _on_enable(self): self._emission = True return self._emission @@ -180,7 +200,7 @@ def _on_shutdown(self): def initialize(self): pass - def disable(self): + def _on_disable(self): self._emission = False return self._emission @@ -188,14 +208,17 @@ def get_is_on(self): return self._emission def _set_power_mw(self, level): + self._logger.info("Power set to %s." % level) self._power = level def get_max_power_mw(self): return 100 def get_power_mw(self): - return self._power + return [0, self._power][self._emission] + +@Pyro4.expose class TestDeformableMirror(devices.DeformableMirror): def __init__(self, n_actuators, *args, **kwargs): super(TestDeformableMirror, self).__init__(*args, **kwargs) @@ -204,3 +227,125 @@ def __init__(self, n_actuators, *args, **kwargs): def apply_pattern(self, pattern): self._validate_patterns(pattern) self._current_pattern = pattern + + +@Pyro4.expose +@Pyro4.behavior('single') +class DummySLM(devices.Device): + def __init__(self, *args, **kwargs): + devices.Device.__init__(self, args, kwargs) + self.sim_diffraction_angle = 0. + self.sequence_params = [] + self.sequence_index = 0 + + def initialize(self, *args, **kwargs): + pass + + def _on_shutdown(self): + pass + + def set_sim_diffraction_angle(self, theta): + self._logger.info('set_sim_diffraction_angle %f' % theta) + self.sim_diffraction_angle = theta + + def get_sim_diffraction_angle(self): + return self.sim_diffraction_angle + + def run(self): + self.enabled = True + self._logger.info('run') + return + + def stop(self): + self.enabled = False + self._logger.info('stop') + return + + def get_sim_sequence(self): + return self.sequence_params + + def set_sim_sequence(self, seq): + self._logger.info('set_sim_sequence') + self.sequence_params = seq + return + + def get_sequence_index(self): + return self.sequence_index + + +@Pyro4.expose +@Pyro4.behavior('single') +class DummyDSP(devices.Device): + def __init__(self, *args, **kwargs): + devices.Device.__init__(self, args, kwargs) + self._digi = 0 + self._ana = [0,0,0,0] + self._client = None + self._actions = [] + + def initialize(self, *args, **kwargs): + pass + + def _on_shutdown(self): + pass + + def Abort(self): + self._logger.info('Abort') + + def WriteDigital(self, value): + self._logger.info('WriteDigital: %s' % "{0:b}".format(value)) + self._digi = value + + def MoveAbsolute(self, aline, pos): + self._logger.info('MoveAbsoluteADU: line %d, value %d' % (aline, pos)) + self._ana[aline] = pos + + def arcl(self, mask, pairs): + self._logger.info('arcl: %s, %s' % (mask, pairs)) + + def profileSet(self, pstr, digitals, *analogs): + self._logger.info('profileSet ...') + self._logger.info('... ', pstr) + self._logger.info('... ', digitals) + self._logger.info('... ', analogs) + + def DownloadProfile(self): + self._logger.info('DownloadProfile') + + def InitProfile(self, numReps): + self._logger.info('InitProfile') + + def trigCollect(self, *args, **kwargs): + self._logger.info('trigCollect: ... ') + self._logger.info(args) + self._logger.info(kwargs) + + def ReadPosition(self, aline): + self._logger.info('ReadPosition : line %d, value %d' % (aline, self._ana[aline])) + return self._ana[aline] + + def ReadDigital(self): + self._logger.info('ReadDigital: %s' % "{0:b}".format(self._digi)) + return self._digi + + def PrepareActions(self, actions, numReps=1): + self._logger.info('PrepareActions') + self._actions = actions + self._repeats = numReps + + def RunActions(self): + self._logger.info('RunActions ...') + for i in range(self._repeats): + for a in self._actions: + self._logger.info(a) + time.sleep(a[0] / 1000.) + if self._client: + self._client.receiveData("DSP done") + self._logger.info('... RunActions done.') + +if sys.version_info[0] < 3: + DummyDSP.receiveClient = devices.DataDevice.receiveClient.im_func + DummyDSP.set_client = devices.DataDevice.set_client.im_func +else: + DummyDSP.receiveClient = devices.DataDevice.receiveClient + DummyDSP.set_client = devices.DataDevice.set_client diff --git a/microscope/testsuite/deviceserver_test.py b/microscope/testsuite/deviceserver_test.py index f8e7da64..6ecf1211 100644 --- a/microscope/testsuite/deviceserver_test.py +++ b/microscope/testsuite/deviceserver_test.py @@ -40,7 +40,7 @@ class BaseTestServeDevices(unittest.TestCase): p (multiprocessing.Process): device server process. """ DEVICES = [] - TIMEOUT = 2 + TIMEOUT = 5 def setUp(self): init = microscope.deviceserver.serve_devices self.p = multiprocessing.Process(target=init, args=(self.DEVICES,)) diff --git a/microscope/testsuite/test_devices.py b/microscope/testsuite/test_devices.py index 9d340579..359682b3 100644 --- a/microscope/testsuite/test_devices.py +++ b/microscope/testsuite/test_devices.py @@ -19,11 +19,15 @@ ## along with Microscope. If not, see . import unittest +import unittest.mock import numpy +import serial import six import microscope.testsuite.devices as dummies +import microscope.testsuite.mock_devices as mocks + class TestDeformableMirror(unittest.TestCase): def setUp(self): @@ -78,5 +82,128 @@ def test_validate_pattern(self): self.dm.apply_pattern(patterns) +class TestSerialMock(unittest.TestCase): + ## Our tests for serial devices depend on our SerialMock base class + ## working properly so yeah, we need tests for that too. + class Serial(mocks.SerialMock): + eol = b'\r\n' + def handle(self, command): + if command.startswith(b'echo '): + self.in_buffer.write(command[5:] + self.eol) + elif command in [b'foo', b'bar']: + pass + else: + raise RuntimeError("unknown command '%s'" % command.decode()) + + def setUp(self): + self.serial = TestSerialMock.Serial() + patcher = unittest.mock.patch.object(TestSerialMock.Serial, 'handle', + wraps=self.serial.handle) + self.addCleanup(patcher.stop) + self.mock = patcher.start() + + def test_simple_commands(self): + self.serial.write(b'foo\r\n') + self.mock.assert_called_once_with(b'foo') + + def test_partial_commands(self): + self.serial.write(b'fo') + self.serial.write(b'o') + self.serial.write(b'\r\n') + self.serial.handle.assert_called_once_with(b'foo') + + def test_multiple_commands(self): + self.serial.write(b'foo\r\nbar\r\n') + calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] + self.assertEqual(self.serial.handle.mock_calls, calls) + + def test_unix_eol(self): + self.serial.eol = b'\n' + self.serial.write(b'foo\nbar\n') + calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] + self.assertEqual(self.serial.handle.mock_calls, calls) + + def test_write(self): + self.serial.write(b'echo qux\r\n') + self.assertEqual(self.serial.readline(), b'qux\r\n') + + +class LaserTests: + """Base class for :class:`LaserDevice` tests. + + This class implements all the general laser tests and is meant to be + mixed with :class:`unittest.TestCase`. The subclass must implement + the `setUp` method which must add two properties: + + `device` + Instance of the :class:`LaserDevice` implementation being tested. + + `laser` + Object with a multiple attributes that specify the hardware and + control the tests, such as the device max and min power values. + Such attributes may as well be attributes in the class that mocks + the hardware. + """ + def __init__(self): + self.laser = None + self.device = None + + def test_connection_defaults(self): + self.assertEqual(self.laser.connection.baudrate, self.device.baudrate) + self.assertEqual(self.laser.connection.parity, self.device.parity) + self.assertEqual(self.laser.connection.bytesize, self.device.bytesize) + self.assertEqual(self.laser.connection.stopbits, self.device.stopbits) + self.assertEqual(self.laser.connection.rtscts, self.device.rtscts) + self.assertEqual(self.laser.connection.dsrdtr, self.device.dsrdtr) + + def test_being(self): + self.assertTrue(self.laser.is_alive()) + + def test_turning_on_and_off(self): + self.assertTrue(self.laser.get_is_on()) + self.laser.disable() + self.assertFalse(self.laser.get_is_on()) + self.laser.enable() + self.assertTrue(self.laser.get_is_on()) + + def test_query_power_range(self): + min_mw = self.laser.get_min_power_mw() + max_mw = self.laser.get_max_power_mw() + self.assertIsInstance(min_mw, float) + self.assertIsInstance(max_mw, float) + self.assertEqual(round(min_mw), self.device.min_power) + self.assertEqual(round(max_mw), self.device.max_power) + + def test_setting_power(self): + power = self.laser.get_power_mw() + self.assertIsInstance(power, float) + self.assertEqual(round(power), self.device.default_power) + + new_power = (self.device.min_power + + ((self.device.max_power - self.device.min_power) /2.0)) + self.laser.set_power_mw(new_power) + self.assertEqual(round(self.laser.get_power_mw()), round(new_power)) + + def test_setting_power_outside_limit(self): + below_limit = self.device.min_power - 10.0 + above_limit = self.device.max_power + 10.0 + self.laser.set_power_mw(below_limit) + self.assertEqual(self.laser.get_power_mw(), self.laser.get_min_power_mw(), + 'clip setting power to the valid range') + self.laser.set_power_mw(above_limit) + self.assertEqual(self.laser.get_power_mw(), self.laser.get_max_power_mw(), + 'clip setting power to the valid range') + + +class TestCoherentSapphireLaser(unittest.TestCase, LaserTests): + def setUp(self): + from microscope.lasers.sapphire import SapphireLaser + from microscope.testsuite.mock_devices import CoherentSapphireLaserMock + with unittest.mock.patch('microscope.lasers.sapphire.serial.Serial', + new=CoherentSapphireLaserMock): + self.laser = SapphireLaser('/dev/null') + self.device = CoherentSapphireLaserMock + + if __name__ == '__main__': unittest.main() diff --git a/setup.py b/setup.py index ce445ed0..e02f1651 100644 --- a/setup.py +++ b/setup.py @@ -8,26 +8,36 @@ ## notice and this notice are preserved. This file is offered as-is, ## without any warranty. +import distutils.cmd +import sys + import setuptools import setuptools.command.sdist -import sys -import sphinx.setup_command +## setup.py is used for both maintainers actions (build documentation, +## run testuite, etc), and users actions (mainly install). We need to +## be careful to not require maintainer tools (such as sphinx) for +## users actions. See issue #47. -try: # In sphinx 1.7, apidoc was moved to the ext subpackage - import sphinx.ext.apidoc as apidoc +has_sphinx = True +try: +import sphinx.setup_command except ImportError: - import sphinx.apidoc as apidoc + has_sphinx = False -try: # In Python 3.3, mock was merged into unittest package +## Since Python 3.3, the mock package is included in the unittest +## package which is part of the Python standard library. +has_mock = True +try: import unittest.mock as mock except ImportError: + try: import mock - -import microscope.testsuite.libs + except ImportError: + has_mock = False project_name = 'microscope' -project_version = '0.1.0+dev' +project_version = '0.2.0+dev' extra_requires = [] @@ -41,16 +51,39 @@ ## Shadow the sphinx provided command, in order to run sphinx-apidoc ## before sphinx-build. This builds the rst files with the actual ## package inline documentation. +if has_sphinx and has_mock: + try: # In sphinx 1.7, apidoc was moved to the ext subpackage + import sphinx.ext.apidoc as apidoc + ## In addition of changing the subpackage, the signature for main() + ## also changed https://github.com/sphinx-doc/sphinx/issues/5088 If + ## we are building in older versions, the program name needs to be + ## included in the args passed to apidoc.main() + apidoc_ini_args = [] + except ImportError: + import sphinx.apidoc as apidoc + apidoc_ini_args = ['sphinx-apidoc'] + + import microscope.testsuite.libs + class BuildDoc(sphinx.setup_command.BuildDoc): @mock.patch('ctypes.CDLL', new=microscope.testsuite.libs.CDLL) def run(self): - apidoc.main(["sphinx-apidoc", + apidoc.main(apidoc_ini_args + [ "--separate", # each module on its own page "--module-first", "--output-dir", "doc/api", "microscope"]) sphinx.setup_command.BuildDoc.run(self) +else: + class BuildDoc(distutils.cmd.Command): + user_options = [] + def __init__(self, *args, **kwargs): + raise RuntimeError(('sphinx and mock are required to build the' + ' documentation')) + + + ## Modify the sdist command class to include extra files in the source ## distribution. Seems a bit ridiculous that we have to do this but ## the only alternative is to have a MANIFEST file and we don't want @@ -75,21 +108,17 @@ def make_distribution(self): name = project_name, version = project_version, description = "An extensible microscope hardware interface.", + long_description = open('README', 'r').read(), license = "GPL-3.0+", - ## Do not use author_email because that won't play nice once there - ## are multiple authors. - author = "Mick Phillips ", + ## We need an author and an author_email value or PyPI rejects us. + ## For multiple authors, they tell us to get a mailing list :/ + author = "See homepage for a complete list of contributors", + author_email = " ", url = "https://github.com/MicronOxford/microscope", - packages = [ - "microscope", - "microscope.cameras", - "microscope.lasers", - "microscope.testsuite", - "microscope._wrappers", - ], + packages = setuptools.find_packages(), install_requires = [ "numpy", From 311c2d6e5a01e91b51da20f8f54afff761b0222d Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 12:36:03 +0200 Subject: [PATCH 20/27] Used _call_if_callable --- microscope/devices.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/microscope/devices.py b/microscope/devices.py index 2e5f4508..74493bd4 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -48,7 +48,7 @@ (TRIGGER_AFTER, TRIGGER_BEFORE, TRIGGER_DURATION, TRIGGER_SOFT) = range(4) # Device types. -(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER, UWAVEFRONTSENSOR) = range(7) +(UGENERIC, USWITCHABLE, UDATA, UCAMERA, ULASER, UFILTER) = range(6) # Mapping of setting data types to descriptors allowed-value description types. # For python 2 and 3 compatibility, we convert the type into a descriptor string. @@ -292,16 +292,9 @@ def update_settings(self, incoming, init=False): results[key] = NotImplemented update_keys.remove(key) continue - if callable(self.settings[key]['readonly']): - if self.settings[key]['readonly'](): - continue - else: - self.settings[key]['set'](incoming[key]) - else: - if self.settings[key]['readonly']: - continue - else: - self.settings[key]['set'](incoming[key]) + if _call_if_callable(self.settings[key]['readonly']): + continue + self.settings[key]['set'](incoming[key]) # Read back values in second loop. for key in update_keys: @@ -947,7 +940,17 @@ def _set_power_mw(self, mw): @Pyro4.expose def set_power_mw(self, mw): - """Set the power from an argument in mW and save the set point.""" + """Set the power from an argument in mW and save the set point. + + Args: + mw (float): Power in mW. Value will be clipped to the + valid range for the laser. See the methods + :func:`get_max_power_mw` and :func:`get_min_power_mw` + to retrieve the valid range. + + Returns: + void + """ self._set_point = mw self._set_power_mw(mw) From a64c9e4ff404b9ceac55864215e203bc8a24e430 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 13:41:04 +0200 Subject: [PATCH 21/27] Split integration of SID4 --- doc/supported-devices.rst | 1 - microscope/wavefront_sensors/SID4_SDK.py | 992 --------------------- microscope/wavefront_sensors/SID4_SDK_defs | 353 -------- microscope/wavefront_sensors/__init__.py | 0 4 files changed, 1346 deletions(-) delete mode 100644 microscope/wavefront_sensors/SID4_SDK.py delete mode 100644 microscope/wavefront_sensors/SID4_SDK_defs delete mode 100644 microscope/wavefront_sensors/__init__.py diff --git a/doc/supported-devices.rst b/doc/supported-devices.rst index 200cd720..ee390fe8 100644 --- a/doc/supported-devices.rst +++ b/doc/supported-devices.rst @@ -31,6 +31,5 @@ Lasers ------ - Cobolt (:class:`microscope.lasers.cobolt`) -- Coherent Obis (:class:`microscope.lasers.obis`) - Coherent Sapphire (:class:`microscope.lasers.saphhire`) - Omicron Deepstar (:class:`microscope.lasers.deepstar`) diff --git a/microscope/wavefront_sensors/SID4_SDK.py b/microscope/wavefront_sensors/SID4_SDK.py deleted file mode 100644 index f12a75cf..00000000 --- a/microscope/wavefront_sensors/SID4_SDK.py +++ /dev/null @@ -1,992 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -# -# Copyright 2016 Julio Mateos Langerak (julio.mateos-langerak@igh.cnrs.fr) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -"""SID4_SDK wavefront sensor device. - -This module provides a wrapper for SID4's SDK interface that allows -a SID4 wavefront sensor from Phasics and all its settings to be exposed over Pyro. - -The interface with the SID4 SDK has been implemented using cffi API 'in line' -""" -# TODO: implement timeout setting -# TODO: Better error handling -# TODO: write UniTests - -from microscope import devices -from microscope.devices import WavefrontSensorDevice, keep_acquiring -from cffi import FFI -import Pyro4 -import numpy as np -import time -# Python 2.7 to 3 -try: - import queue -except: - import Queue as queue - -# Trigger mode to type. -TRIGGER_MODES = { - 0: devices.TRIGGER_SOFT, # 0:'continuous mode' - 1: devices.TRIGGER_BEFORE, # 1:'mode0' - 2: devices.TRIGGER_DURATION # 2:'mode1' -} -FRAME_RATES = { - 1: '3.75hz', - 2: '7.5hz', - 3: '15hz', - 4: '30hz', - 5: '60hz' -} -EXPOSURE_TIMES = { # Asa string and in msec - 0: ('1/60s', 16.6667), - 1: ('1/200s', 5.0), - 2: ('1/500s', 2.0), - 3: ('1/1000s', 1.0), - 4: ('1/2000s', 0.5), - 5: ('1/4000s', 0.25), - 6: ('1/8000s', 0.125), - 7: ('1/20000s', 0.05) -} -REFERENCE_SOURCES = { - 0: 'SDK_WORKSHOP', # Workshop reference - 1: 'SDK_CAMERA', # Reference from a grabbed camera image - 2: 'SDK_FILE' # Reference from an interferogram file, which path is given by ReferencePath -} -ZERNIKE_BASES = {0: 'Zernike', 1: 'Legendre'} -CAMERA_ATTRIBUTES = { - 'Exposure': (0, 0, 7), - 'Gain': (1, 40, 210), - 'GainOffset': (2, -1, -1), - 'Trigger': (3, 0, 2), - 'FrameRate': (4, 1, 5), - 'ImageWidth': (5, -1, -1), - 'ImageHeight': (6, -1, -1), - 'TimeOut': (7, 1, 10000), - 'AE_On/Off': (8, -1, -1) -} - -INVALIDATES_SETTINGS = ['_simple_pre_amp_gain_control', '_pre_amp_gain_control', - '_aoi_binning', '_aoi_left', '_aoi_top', - '_aoi_width', '_aoi_height', ] - -# We setup some necessary configuration parameters. TODO: Move this into the config file -# This is the default profile path of the camera -WFS_PROFILE_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\SID4-079b default profile.txt' -WFS_MASK_PATH = b'C:\\Users\\omxt\\Documents\\PHASICS\\User Profiles\\SID4-079b default profile\\Imasque.msk' - -# We import the headers definitions used by cffi from a file in order to avoid copyright issues -HEADER_DEFINITIONS = "C:\\Users\\omxt\\PycharmProjects\\microscope\\microscope\\wavefront_sensors\\SID4_SDK_defs" -SID4_SDK_DLL_PATH = "SID4_SDK.dll" -ZERNIKE_SDK_DLL_PATH = "Zernike_SDK.dll" - -@Pyro4.expose -# @Pyro4.behavior('single') -class SID4Device(WavefrontSensorDevice): - """ - This class represents the SID4 wavefront sensors - Important note: The header defs is a text file containing the headers from - the SID4_SDK.h file with some modifications. Namely all #include and #ifdef have been - removed. Also, the typedef of LVBoolean has been changed for 'unsigned char' - - :param header_definitions: Absolute path to the header definitions from the SDK. - :param sid4_sdk_dll_path: name of, or absolute path to, the SID4_SDK.dll file - :param zernike_sdk_dll_path: name of, or absolute path to, the Zernike_SDK.dll file - :param wfs_profile_path: Absolute path to the profile file that has to be loaded at startup. - Must be a byte encoded string. - :param camera_attributes: This is a dictionary containing a reference to the camera attributes - returned by the SDK's 'Camera_GetAttributeList'. The dictionary has as keys the attribute name - and as values a tuple containing (attribute_id, min_value, max_value). - """ - def __init__(self, - header_definitions=HEADER_DEFINITIONS, - sid4_sdk_dll_path=SID4_SDK_DLL_PATH, - zernike_sdk_dll_path=ZERNIKE_SDK_DLL_PATH, - wfs_profile_path=WFS_PROFILE_PATH, - wfs_mask_path=WFS_MASK_PATH, - camera_attributes=CAMERA_ATTRIBUTES, - **kwargs): - self.header_definitions = header_definitions - try: - with open(self.header_definitions, 'r') as self.header_definitions: - self.cdef_from_file = self.header_definitions.read() - except FileNotFoundError: - print('Unable to find "%s" header file.' % self.header_definitions) - exit(1) - except IOError: - print('Unable to open "%s"' % self.header_definitions) - exit(2) - finally: - if self.cdef_from_file == '' or None: - print('File "%s" is empty' % self.header_definitions) - exit(3) - - # Create here the interface to the SDK - self.ffi = FFI() - self.ffi.cdef(self.cdef_from_file, override=True) - self.SID4_SDK = self.ffi.dlopen(sid4_sdk_dll_path) - self.zernike_SDK = self.ffi.dlopen(zernike_sdk_dll_path) - - # Allocate all necessary instances to control the SID4 - self.buffer_size = 1024 # buffer size to be used to initialize some variables - - # Create main instances - self.session_id = self.ffi.new('SDK_Reference *') - self.error_code = self.ffi.new('long *', 0) - - # Create metadata instances - self.user_profile_name = self.ffi.new("char[]", self.buffer_size) - self.user_profile_name_bs = self.ffi.cast("long", self.buffer_size) - - self.user_profile_file = self.ffi.new("char[]", self.buffer_size) - self.user_profile_file_bs = self.ffi.cast("long", self.buffer_size) - self.user_profile_file = wfs_profile_path - - self.user_profile_description = self.ffi.new("char[]", self.buffer_size) - self.user_profile_description_bs = self.ffi.cast("long", self.buffer_size) - - self.user_profile_last_reference = self.ffi.new("char[]", self.buffer_size) - self.user_profile_last_reference_bs = self.ffi.cast("long", self.buffer_size) - - self.user_profile_directory = self.ffi.new("char[]", self.buffer_size) - self.user_profile_directory_bs = self.ffi.cast("long", self.buffer_size) - - self.sdk_version = self.ffi.new("char[]", self.buffer_size) - self.sdk_version_bs = self.ffi.cast("long", self.buffer_size) - - self.analysis_information = self.ffi.new("AnalysisInfo *") - self.camera_information = self.ffi.new("CameraInfo *") - - self.reference_source = self.ffi.cast("unsigned short int", 0) - self.reference_path = self.ffi.new("char[]", self.buffer_size) - self.reference_changed = self.ffi.cast("unsigned char", 0) - - self.camera_sn = self.ffi.new("char[]", self.buffer_size) - self.camera_sn_bs = self.ffi.cast("long", self.buffer_size) - - # self.camera_array_size = self.ffi.new("ArraySize *") - self.interferogram_array_size = self.ffi.new("ArraySize *") - self.analysis_array_size = self.ffi.new("ArraySize *") - - # Create ROI and pupil (mask) related parameters - self.user_mask_file = self.ffi.new("char[]", self.buffer_size) - self.user_mask_file_bs = self.ffi.cast("long", self.buffer_size) - self.user_mask_file = wfs_mask_path - - self.roi_global_rectangle = self.ffi.new("long []", 4) - self.roi_global_rectangle_bs = self.ffi.cast("long", 4) - - self.roi_nb_contours = self.ffi.new("unsigned short int *", 1) - - self.roi_contours_info = self.ffi.new("unsigned long []", 3) - self.roi_contours_info_bs = self.ffi.cast("long", 3) - - self.roi_contours_coordinates = self.ffi.new("long []", 4) - self.roi_contours_coordinates_bs = self.ffi.cast("long", 4) - - # Create zernike-related parameters - self.zernike_information = self.ffi.new("ZernikeInformation *") - self.zernike_parameters = self.ffi.new("ZernikeParam *") - - self.zernike_version = self.ffi.new("char[]", self.buffer_size) - self.zernike_version_bs = self.ffi.cast("long", self.buffer_size) - - self.polynomials_list = self.ffi.new("char[]", self.buffer_size) - self.polynomials_list_bs = self.ffi.cast("long", self.buffer_size) - - self.zernike_array_size = self.ffi.new("ArraySize *") - self.zernike_orders = self.ffi.cast("unsigned char", 0) - - self.acquisition_buffer = {} - - # Call super __init__. - super(SID4Device, self).__init__(**kwargs) - - # Create camera attributes. TODO: this should be created programmatically - self.camera_attributes = camera_attributes - - # We compute a dict with exposure_times (in seconds) to Index. - self.exposure_time_s_to_index = {} - for key, exposure in EXPOSURE_TIMES.items(): - self.exposure_time_s_to_index[exposure[1]] = key - - # TODO: Soft_trigger - self._is_software_trigger = True - # HACK: as fetching the data from the camera is triggering we use this - # variable to control that not the same data is acquired twice. Note the camera - # uses no buffers - self._valid_data = False - - # Add profile settings - self.add_setting('user_profile_name', 'str', - lambda: self.ffi.string(self.user_profile_name), - None, - self.buffer_size, - readonly=True) - self.add_setting('user_profile_file', 'str', - lambda: self.ffi.string(self.user_profile_file), - None, - self.buffer_size, - readonly=True) - self.add_setting('user_profile_description', 'str', - lambda: self.ffi.string(self.user_profile_description), # TODO: Fix here - None, - self.buffer_size, - readonly=True) - self.add_setting('user_profile_last_reference', 'str', - lambda: self.ffi.string(self.user_profile_last_reference), - None, - self.buffer_size, - readonly=True) - self.add_setting('user_profile_last_directory', 'str', - lambda: self.ffi.string(self.user_profile_directory), - None, - self.buffer_size, - readonly=True) - self.add_setting('sdk_version', 'str', - lambda: self.ffi.string(self.sdk_version), - None, - self.buffer_size, - readonly=True) - self.add_setting('camera_sn', 'str', - lambda: self.ffi.string(self.camera_sn), - None, - self.buffer_size, - readonly=True) - - # Add camera settings - self.add_setting('frame_rate', 'enum', - self._get_frame_rate, - self._set_frame_rate, - lambda: FRAME_RATES.keys()) - self.add_setting('trigger_mode', 'enum', - lambda: TRIGGER_MODES[self.camera_information.TriggerMode], - self._set_trigger_mode, - lambda: TRIGGER_MODES.keys()) - self.add_setting('gain', 'int', - lambda: self.camera_information.Gain, - self._set_gain, - lambda: (40, 210)) - self.add_setting('exposure_time', 'enum', - lambda: self.camera_information.ExposureTime, - self._set_exposure_time, - lambda: EXPOSURE_TIMES.keys()) - self.add_setting('exposure_time_ms', 'float', - lambda: EXPOSURE_TIMES[self.camera_information.ExposureTime][1], - None, # The SID4 does not support an arbitrary exposure time. - lambda: (0.05, 17.0), - readonly=True) - self.add_setting('camera_pixel_size_m', 'float', - lambda: self.camera_information.PixelSizeM, - None, - lambda: (0.0, 0.1), - readonly=True) - self.add_setting('camera_number_rows', 'int', - lambda: self._get_camera_attribute('ImageHeight'), - None, - lambda: (0, 480), - readonly=True) - self.add_setting('camera_number_cols', 'int', - lambda: self._get_camera_attribute('ImageWidth'), - None, - lambda: (0, 640), - readonly=True) - self.add_setting('number_of_camera_recorded', 'int', - lambda: self.camera_information.NumberOfCameraRecorded, - None, - lambda: (0, 255), - readonly=True) - - # Add analysis settings - self.add_setting('reference_source', 'enum', - lambda: int(self.reference_source), # TODO: verify returns 0 - self._set_reference_source, - lambda: REFERENCE_SOURCES.keys()) - self.add_setting('reference_path', 'str', - lambda: self.ffi.string(self.reference_path), # TODO: verify - self._set_reference_path, - self.buffer_size) - self.add_setting('grating_position_mm', 'float', - lambda: self.analysis_information.GratingPositionMm, - None, - (0.0, 300.0), # TODO: Verify these values - readonly=True) - self.add_setting('wavelength_nm', 'float', - self._get_wavelength_nm, - self._set_wavelength_nm, - (400.0, 1100.0)) - self.add_setting('remove_background_image', 'bool', - lambda: self.analysis_information.RemoveBackgroundImage, - self._set_remove_background_image, - None) - self.add_setting('phase_size_width', 'int', - self._get_phase_size_width, - None, - (0, 160), - readonly=True) - self.add_setting('phase_size_height', 'int', - self._get_phase_size_height, - None, - (0, 120), - readonly=True) - self.add_setting('zernike_base', 'enum', - lambda: self.zernike_information.Base, - self._set_zernike_base, # TODO: not working - lambda: ZERNIKE_BASES.values()) - self.add_setting('nr_zernike_orders', 'int', - self._get_nr_zernike_orders, - self._set_nr_zernike_orders, - (0, 254)) - self.add_setting('nr_zernike_polynomials', 'int', - self._get_nr_zernike_polynomials, - None, - (0, 36000), - readonly=True) - self.add_setting('zernike_mask_col_size', 'int', - lambda: self.zernike_parameters.MaskColSize, - None, - (0, 160), - readonly=True) - self.add_setting('zernike_mask_row_size', 'int', - lambda: self.zernike_parameters.MaskRowSize, - None, - (0, 120), - readonly=True) - self.add_setting('zernike_version', 'str', - lambda: self.ffi.string(self.zernike_version), # TODO: error - None, - self.buffer_size, - readonly=True) - - def get_id(self): - self.get_setting('camera_sn') - - def get_error(self): - return self.error_code[0] - - def get_camera_session(self): - return self.session_id[0] - - def invalidate_settings(self, func): - """Wrap functions that invalidate settings so settings are reloaded.""" - outerself = self - - def wrapper(self, *args, **kwargs): - func(self, *args, **kwargs) - outerself._settings_valid = False - return wrapper - - def _create_buffers(self): - """Creates a buffer to store the data. It also reloads all necessary parameters""" - self._refresh_zernike_attributes() - - self.analysis_array_size.nCol = self.get_setting('phase_size_width') - self.analysis_array_size.nRow = self.get_setting('phase_size_height') - - # TODO: split phase and intensity map sizes. They are not necessarily the same - phase_map_width = intensity_map_width = self.get_setting('phase_size_width') - phase_map_height = intensity_map_height = self.get_setting('phase_size_height') - nr_zernike_polynomials = self.get_setting('nr_zernike_polynomials') - phase_map_size = intensity_map_size = phase_map_width * phase_map_height - - phase_map = self.ffi.new("float[]", phase_map_size) - phase_map_bs = self.ffi.cast("long", phase_map_size) - np_phase_map = np.frombuffer(buffer=self.ffi.buffer(phase_map), - dtype='float32') - np_phase_map.shape = (phase_map_width, phase_map_height) - - intensity_map = self.ffi.new("float[]", intensity_map_size) - intensity_map_bs = self.ffi.cast("long", intensity_map_size) - np_intensity_map = np.frombuffer(buffer=self.ffi.buffer(intensity_map), - dtype='float32') - np_intensity_map.shape = (intensity_map_width, intensity_map_height) - - tilt = self.ffi.new('TiltInfo *') - np_tilt = np.frombuffer(buffer=self.ffi.buffer(tilt), - dtype='float32') - - projection_coefficients = self.ffi.new("double[]", nr_zernike_polynomials) - projection_coefficients_bs = self.ffi.cast("long", nr_zernike_polynomials) - np_projection_coefficients = np.frombuffer(buffer=self.ffi.buffer(projection_coefficients), - dtype='float64') - - self.acquisition_buffer.update({'phase_map': phase_map, - 'phase_map_bs': phase_map_bs, - 'np_phase_map': np_phase_map, - 'intensity_map': intensity_map, - 'intensity_map_bs': intensity_map_bs, - 'np_intensity_map': np_intensity_map, - 'tilts': tilt, - 'np_tilts': np_tilt, - 'zernike_polynomials': projection_coefficients, - 'zernike_polynomials_bs': projection_coefficients_bs, - 'np_zernike_polynomials': np_projection_coefficients}) - - def _grab_live(self): - """Uses the SDK's GrabLiveMode to get the phase and intensity maps and - calls the Zernike functions to calculate the projection of the polynomials""" - try: - self.SID4_SDK.GrabLiveMode(self.session_id, - self.acquisition_buffer['phase_map'], - self.acquisition_buffer['phase_map_bs'], - self.acquisition_buffer['intensity_map'], - self.acquisition_buffer['intensity_map_bs'], - self.acquisition_buffer['tilts'], - self.analysis_array_size, - self.error_code) - except: - self._logger.debug('Could not use GrabLiveMode. Error: ', self.error_code[0]) - Exception('Could not GrabLiveMode') - try: - self.zernike_SDK.Zernike_PhaseProjection(self.acquisition_buffer['phase_map'], - self.acquisition_buffer['phase_map_bs'], - self.zernike_array_size, - self.acquisition_buffer['zernike_polynomials'], - self.acquisition_buffer['zernike_polynomials_bs'], - self.error_code) - except: - self._logger.debug('Could not use PhaseProjection. Error: ', self.error_code[0]) - Exception('Could not get PhaseProjection') - - @Pyro4.expose() - def soft_trigger(self): - if self._acquiring and self._is_software_trigger: - self._grab_live() - self._valid_data = True - else: - raise Exception('cannot trigger if camera is not acquiring or is not in software trigger mode.') - - def _fetch_data(self, timeout=5, debug=False): - if not self._is_software_trigger: # Camera is not using software trigger - self._grab_live() - else: # Camera is using software trigger: - if not self._valid_data: - return None - - self._valid_data = False - - return {'phase_map': np.copy(self.acquisition_buffer['np_phase_map']), - 'intensity_map': np.copy(self.acquisition_buffer['np_intensity_map']), - 'tilts': np.copy(self.acquisition_buffer['np_tilts']), - 'zernike_polynomials': np.copy(self.acquisition_buffer['np_zernike_polynomials'])} - - def _process_data(self, data): - """Apply necessary transformations to data to be served to the client. - - Return as a dictionary: - - intensity_map: a 2D array containing the intensity - - linearity: some measure of the linearity of the data. - Simple saturation at the intensity map might not be enough to indicate - if we are exposing correctly to get a accurate measure of the phase. - - phase_map: a 2D array containing the phase - - tilts: a tuple containing X and Y tilts - - RMS: the root mean square measurement - - PtoV: peak to valley measurement - - zernike_polynomials: a list with the relevant Zernike polynomials - """ - trimmed_phase_map = self._trim_zeros(data['phase_map']) - processed_data = {'intensity_map': self._apply_transform(data['intensity_map']), - 'phase_map': self._apply_transform(data['phase_map']), - 'tilts': data['tilts'], - 'zernike_polynomials': data['zernike_polynomials'], - 'RMS': trimmed_phase_map.std(), - 'PtoV': trimmed_phase_map.ptp()} - - return processed_data - - def _apply_transform(self, array): - """Apply self._transform to a numpy array""" - flips = (self._transform[0], self._transform[1]) - rot = self._transform[2] - - # Choose appropriate transform based on (flips, rot). - return {(0, 0): np.rot90(array, rot), - (0, 1): np.flipud(np.rot90(array, rot)), - (1, 0): np.fliplr(np.rot90(array, rot)), - (1, 1): np.fliplr(np.flipud(np.rot90(array, rot))) - }[flips] - - def _trim_zeros(self, data): - """Returns a linear numpy array where the zeros outside the pupil have been trimmed""" - trimmed_data = np.array([]) - for row in data: - trimmed_data = np.append(trimmed_data, np.trim_zeros(row)) - return trimmed_data - - def abort(self): - """Abort acquisition.""" - self._logger.debug('Aborting acquisition.') - if self._acquiring: - self._acquisition_stop() - - def initialize(self): - """Initialize the SID4 - - Opens the connection to the SDK, initializes the SID4 and populates the settings - from the input profile""" - self._logger.debug('Opening SDK...') - try: - self.SID4_SDK.OpenSID4(self.user_profile_file, self.session_id, self.error_code) - self._refresh_user_profile_params() - except: - raise Exception('SDK could not open.') - - # update the settings that are not implemented through the user profile or the camera attributes - self.update_settings(settings={'nr_zernike_orders': 5}) - - # Load zernike analysis settings - self._refresh_zernike_attributes() - self._logger.debug('Loading zernike analysis settings...') - - # Load mask descriptor file - self._load_pupil_descriptor() - self._logger.debug('Loading pupil settings...') - - self._logger.debug('Initializing SID4...') - try: - self.SID4_SDK.CameraInit(self.session_id, self.error_code) - except: - raise Exception('SID4 could not Init. Error code: ', self.error_code[0]) - self._logger.debug('...SID4 Initialized') - - def _on_enable(self): - self._logger.debug('Enabling SID4.') - if self._acquiring: - self._acquisition_stop() - self._logger.debug('Starting SID4 acquisition...') - - self._create_buffers() - - self._acquisition_start() - self._logger.debug('Acquisition enabled: %s' % self._acquiring) - return True - - def _acquisition_start(self): - try: - self.SID4_SDK.CameraStart(self.session_id, self.error_code) - if not self.error_code[0]: - self._acquiring = True - except: - raise Exception('Unable to enable SID4. Error code: ', str(self.error_code[0])) - - def _acquisition_stop(self): - try: - self.SID4_SDK.CameraStop(self.session_id, self.error_code) - if not self.error_code[0]: - self._acquiring = False - except: - raise Exception('Unable to stop acquisiiton. Error code: ', str(self.error_code[0])) - - def _on_disable(self): - self.abort() - - def _on_shutdown(self): - self.disable() - try: - self.SID4_SDK.CameraClose(self.session_id, self.error_code) - except: - raise Exception('Unable to close camera. Error code: ', str(self.error_code[0])) - - try: - self.SID4_SDK.CloseSID4(self.session_id, self.error_code) - except: - raise Exception('Unable to close SDK. Error code: ', str(self.error_code[0])) - - @keep_acquiring - def _refresh_user_profile_params(self): - """Sets the user profile file but also reloads the SDK with OpenSID4 and - repopulates the settings with GetUserProfile""" - try: - self.SID4_SDK.GetUserProfile(self.session_id, - self.user_profile_name, - self.user_profile_name_bs, - self.user_profile_file, - self.user_profile_file_bs, - self.user_profile_description, - self.user_profile_description_bs, - self.user_profile_last_reference, - self.user_profile_last_reference_bs, - self.user_profile_directory, - self.user_profile_directory_bs, - self.sdk_version, - self.sdk_version_bs, - self.analysis_information, - self.camera_information, - self.camera_sn, - self.camera_sn_bs, - self.interferogram_array_size, - self.error_code) - - except: - raise Exception('SDK could not open. Error code: ', self.error_code[0]) - - def _refresh_zernike_attributes(self): - """Applies changes to the zernike parameters. - :returns True if sucessful and False otherwise""" - self.zernike_parameters.ImageRowSize = self.zernike_array_size.nCol = self.get_setting('phase_size_width') - self.zernike_parameters.ImageColSize = self.zernike_array_size.nRow = self.get_setting('phase_size_height') - # TODO: the two following are not necessarily true - self.zernike_parameters.MaskRowSize = self.get_setting('phase_size_width') - self.zernike_parameters.MaskColSize = self.get_setting('phase_size_height') - - try: - self.zernike_SDK.Zernike_UpdateProjection_fromParameter(self.zernike_parameters, - self.zernike_orders, - self.error_code) - except: - Exception('Could not update zernike parameters') - return False - try: - self.zernike_SDK.Zernike_GetZernikeInfo(self.zernike_information, - self.zernike_array_size, - self.zernike_version, - self.zernike_version_bs) - except: - Exception('Could not get zernike info') - return False - - return True - - def _get_camera_attribute(self, attribute): - attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) - value = self.ffi.new('double *') - try: - self.SID4_SDK.Camera_GetAttribute(self.session_id, - attribute_id, - value, - self.error_code) - except: - raise Exception('Could not get camera attribute: %s', attribute) - - return value[0] - - @keep_acquiring - def _set_camera_attribute(self, attribute, value): - attribute_id = self.ffi.cast('unsigned short int', self.camera_attributes[attribute][0]) - new_value = self.ffi.new('double *', value) - try: - self.SID4_SDK.Camera_SetAttribute(self.session_id, - attribute_id, - new_value, - self.error_code) - except: - raise Exception('Could not change camera attribute: %s', attribute) - - def _modify_user_profile(self, save=False): - try: - self.SID4_SDK.ModifyUserProfile(self.session_id, - self.analysis_information, - self.reference_source, - self.reference_path, - self.user_profile_description, - self.reference_changed, - self.error_code) - except: - Exception('Could not modify user profile') - if save: - self._save_user_profile() - - def _save_user_profile(self): - try: - self.SID4_SDK.SaveCurrentUserProfile(self.session_id, self.error_code) - except: - Exception('Could not save user profile') - - def _get_frame_rate(self): - return self._get_camera_attribute(attribute='FrameRate') - - def _set_frame_rate(self, rate): - self._set_camera_attribute('FrameRate', rate) - if not self.error_code[0]: - self.camera_information.FrameRate = rate - - def _set_trigger_mode(self, mode): # TODO: SoftTrigger - self._set_camera_attribute('Trigger', mode) - if not self.error_code[0]: - self.camera_information.TriggerMode = mode - if mode == 0: - self._is_software_trigger = True - self._valid_data = False - else: - self._is_software_trigger = False - self._valid_data = False - - def _set_gain(self, gain): - self._set_camera_attribute('Gain', gain) - if not self.error_code[0]: - self.camera_information.Gain = gain - - def get_exposure_time(self): - return 1000 * self.get_setting('exposure_time_ms') - - def set_exposure_time(self, value): - """The SID4 does not accept arbitrary exposure times but a limited - number of values that are expressed in EXPOSURE_TIMES. - We here take the closest value and apply that exposure time""" - index = min(self.exposure_time_s_to_index, key=lambda x: abs(x - value)) - self._set_exposure_time(index=index) - - def _set_exposure_time(self, index): - if type(index) == str: - index = self.exposure_time_s_to_index['index'] - self._set_camera_attribute('Exposure', index) - if not self.error_code[0]: - self.camera_information.ExposureTime = index - - def _set_reference_source(self, source, save=False): - self.reference_source = source - self._modify_user_profile(save=save) - - def _set_reference_path(self, path, save=False): - self.reference_path = path - self._modify_user_profile(save=save) - - def _get_wavelength_nm(self): - return self.analysis_information.wavelengthNm - - def _set_wavelength_nm(self, wavelength, save=False): - self.analysis_information.wavelengthNm = wavelength - self._modify_user_profile(save=save) - - def _set_remove_background_image(self, remove, save=False): - if remove: - self.analysis_information.RemoveBackgroundImage = 1 - else: - self.analysis_information.RemoveBackgroundImage = 0 - - self._modify_user_profile(save=save) - - def _get_sensor_shape(self): - return (self.get_setting('camera_number_cols'), - self.get_setting('camera_number_rows')) - - def _get_binning(self): - # TODO: Check if it is not better to put here the 'natural' binning that we get after interferogram analysis - return 1, 1 - - def _set_binning(self, h_bin, v_bin): - """binning is not implemented""" - return False - - def _get_roi(self): - roi = (self.roi_global_rectangle[0], - self.roi_global_rectangle[1], - self.roi_global_rectangle[2] - self.roi_global_rectangle[0], - self.roi_global_rectangle[3] - self.roi_global_rectangle[1]) - return roi - - def _set_roi(self, left, top, width, height): - right = left + width - bottom = top + height - self.roi_global_rectangle = [left, top, right, bottom] - return self.set_pupil() - - def _get_pupil(self): - return self._pupil_shape, self._pupil_edge - - def _set_pupil(self, shape, edge): - """Implementation of a simple version to set the pupil to a circle or rectangle - (shape) embedded into the main ROI with a certain margin (edge). It is possible - to generate more complex pupils but I don't currently see the need.""" - self.roi_nb_contours[0] = 1 # number of sub-ROIs defining the pupil - # sub-ROI characteristics, there are three value for each sub-ROI - self.roi_contours_info[0] = 1 # contour is the external (0) or internal edge (1) of an sub-ROI - self.roi_contours_info[1] = 4 # shape of the contour: 3 = Rectangle, 4 = Oval or Circle - self.roi_contours_info[2] = 4 # Nb of coordinates defining the contour. The minimum value is 4 in the case of a simple form (rectangle, circle, ellipse) and can be higher in the case of a polygon - self.roi_contours_info_bs = 3 * self.roi_nb_contours[0] - # Coordinates of the Rectangle of each sub-ROI the ones behind the others. - # They are not referred to the global ROI so we have to calculate them according to it - # Note that the global rectangle must be defined before setting the pupil - self.roi_contours_coordinates[0] = self.roi_global_rectangle[0] + edge - self.roi_contours_coordinates[1] = self.roi_global_rectangle[1] + edge - self.roi_contours_coordinates[2] = self.roi_global_rectangle[2] - edge - self.roi_contours_coordinates[3] = self.roi_global_rectangle[3] - edge - self.roi_contours_coordinates_bs = 4 * self.roi_nb_contours[0] - - return self._change_mask() - - @keep_acquiring - def _change_mask(self): - """Calls SDK ChangeMask using the current parameters. Checks for coherence - Return False if not successful""" - # TODO check coherence - mask_file = self.ffi.new("char[]", self.buffer_size) - mask_file = b'' - try: - self.SID4_SDK.ChangeMask(self.session_id, - mask_file, - self.roi_global_rectangle, - self.roi_global_rectangle_bs, - self.roi_nb_contours, - self.roi_contours_info, - self.roi_contours_info_bs, - self.roi_contours_coordinates, - self.roi_contours_coordinates_bs, - self.error_code) - except: - Exception('Could not change mask') - return False - - return True - - def _load_pupil_descriptor(self, mask_descriptor_path=None): - """Loads the mask parameters from a file""" - if mask_descriptor_path is None: - mask_descriptor_path = self.user_mask_file - - self.error_code[0] = 0 - self.SID4_SDK.LoadMaskDescriptor(self.session_id, - mask_descriptor_path, - self.roi_global_rectangle, - self.roi_global_rectangle_bs, - self.roi_nb_contours, - self.roi_contours_info, - self.roi_contours_info_bs, - self.roi_contours_coordinates, - self.roi_contours_coordinates_bs, - self.error_code) - # catch if the allocated arrays were not enough to contain all subROIs. - if self.error_code[0] == 7005: - self.roi_contours_info = self.ffi.new("unsigned long []", 3 * self.roi_nb_contours[0]) - self.roi_contours_info_bs = self.ffi.cast("long", 3 * self.roi_nb_contours[0]) - - self.roi_contours_coordinates = self.ffi.new("long []", 4 * self.roi_nb_contours[0]) - self.roi_contours_coordinates_bs = self.ffi.cast("long", 4 * self.roi_nb_contours[0]) - self._load_pupil_descriptor(mask_descriptor_path=mask_descriptor_path) - - def _get_phase_size_width(self): - # HACK: This is actually not the right way to collect this info but cffi is - # otherwise getting different bytes from memory - return int.from_bytes(self.ffi.buffer(self.analysis_information)[17:21], 'little') - - def _get_phase_size_height(self): - # HACK: This is actually not the right way to collect this info but cffi is - # otherwise getting different bytes from memory - return int.from_bytes(self.ffi.buffer(self.analysis_information)[21:25], 'little') - - def _set_zernike_base(self, base): - current_base = self.zernike_parameters.Base - self.zernike_parameters.Base = base - if self._refresh_zernike_attributes(): - return - else: - self.zernike_parameters.Base = current_base - - def _get_nr_zernike_orders(self): - return self.zernike_orders - - def _set_nr_zernike_orders(self, orders): - current_orders = self.zernike_orders - self.zernike_orders = orders - if self._refresh_zernike_attributes(): - return - else: - self.zernike_orders = current_orders - - def _get_nr_zernike_polynomials(self): - return int.from_bytes(self.ffi.buffer(self.zernike_information)[2:6], 'little') - - def _set_zernike_mask_row_size(self, row_size): - pass - - def _set_zernike_mask_col_size(self, col_size): - pass - -if __name__ == '__main__': - - wfs = SID4Device() - wfs.initialize() - wfs.enable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.disable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.enable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.disable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.shutdown() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.initialize() - wfs.enable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.disable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.shutdown() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - wfs.enable() - print('Enabled: ' + str(wfs.get_is_enabled())) - print('Acquiring: ' + str(wfs._acquiring)) - - for i in range(3): - wfs.soft_trigger() - - - - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - print('Changing exposure_time') - wfs.set_setting('exposure_time', 2) - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - wfs.soft_trigger() - - print(wfs._fetch_data()['intensity_map'][15, 15]) - print('Changing exposure_time') - wfs.set_setting('exposure_time', 2) - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - print('Changing exposure_time') - wfs.set_setting('exposure_time', 3) - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - print('Changing exposure_time') - wfs.set_setting('exposure_time', 6) - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - print('Changing exposure_time') - wfs.set_setting('exposure_time', 8) - print('Current exposure_time: ', wfs.get_setting('exposure_time')) - for i in range(10): - wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][15, 15]) - exposure_time = wfs.get_setting('exposure_time') - print(exposure_time) - wfs.set_setting('exposure_time', 5) - # wfs.set_setting('exposure_time', '1/200s') - print('Changing Exposure time') - exposure_time = wfs.get_setting('exposure_time') - print(exposure_time) - frame_rate = wfs.get_setting('frame_rate') - print('FrameRate: ', frame_rate) - wfs.set_setting('frame_rate', 1) - frame_rate = wfs.get_setting('frame_rate') - print('FrameRate: ', frame_rate) - for i in range(10): - wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][15,15]) - wfs.set_setting('frame_rate', 5) - frame_rate = wfs.get_setting('frame_rate') - print('FrameRate: ', frame_rate) - print('Changing mask to 80') - wfs.set_roi(100, 100, 80, 80) - for i in range(10): - wfs.soft_trigger() - print(wfs._fetch_data()['intensity_map'][15,15]) - - wfs.shutdown() diff --git a/microscope/wavefront_sensors/SID4_SDK_defs b/microscope/wavefront_sensors/SID4_SDK_defs deleted file mode 100644 index 6d2883e6..00000000 --- a/microscope/wavefront_sensors/SID4_SDK_defs +++ /dev/null @@ -1,353 +0,0 @@ -//****************************************************************// -// This file is containing the headers from the SID4_SDK.h file // -// with some modifications. Namely #include and #ifdef have been // -// removed. Also, the typedef of LVBoolean has been changed // -//****************************************************************// - -#pragma pack(push) -#pragma pack(1) - -// In the original headers LVBoolean is defined as another type -typedef unsigned char LVBoolean; - -typedef int SDK_Reference; - -//****************************************************************// -// Definitions of structures used in the SID4_SDK functions // -//****************************************************************// - -// Tilt Information -typedef struct { - float XTilt; - float YTilt; - } TiltInfo; - -// Size Information on the 2D arrays given as input parameters -typedef struct { - long nRow; - long nCol; - } ArraySize; - - -// Analysis Information, to be used with GetUserProfile -typedef struct { - double GratingPositionMm; - double wavelengthNm; - LVBoolean RemoveBackgroundImage; - long PhaseSize_width; - long PhaseSize_Height; - } AnalysisInfo; - -// Camera Information, to be used with GetUserProfile -typedef struct { - long FrameRate; - unsigned long TriggerMode; - long Gain; - unsigned long ExposureTime; - float PixelSizeM; - unsigned char NumberOfCameraRecorded; - } CameraInfo; - -//**************************************************************// -// SID4_SDK Basic functions // -//**************************************************************// - - -// Configuration functions -void __cdecl OpenSID4(char UserProfileLocation[], SDK_Reference *SessionID, - long *ErrorCode); - -void __cdecl CloseSID4(SDK_Reference *SessionID, long *ErrorCode); - -void __cdecl GetUserProfile(SDK_Reference *SDKSessionID, char UserProfile_Name[], - long uspName_bufSize, char UserProfile_File[], long uspFile_bufSize, - char UserProfile_Description[], long uspDesc_bufSize, - char UsrP_LatestReference[], long uspLastRef_bufSize, - char UserProfile_Directory[], long uspDir_bufSize, char SDKVersion[], - long version_bufSize, AnalysisInfo *AnalysisInformation, CameraInfo *CameraInformation, - char SNPhasics[], long SNPhasics_bufSize, ArraySize *AnalysisArraySize, - long *ErrorCode); - -void __cdecl ChangeReference(SDK_Reference *SDKSessionID, char ReferencePath[], - unsigned short int ReferenceSource, char ArchivedPath[], long ArchivedPath_bufSize, - long *ErrorCode); - - void __cdecl SetBackground(SDK_Reference *SDKSessionID, unsigned short int Source, - char BackgroundFile[], char UpdatedBackgoundImageFile[], - long updatedImageFile_bufSize, long *ErrorCode); - -void __cdecl ChangeMask(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl LoadMaskDescriptorInfo(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl LoadMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int *ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - -void __cdecl ModifyUserProfile(SDK_Reference *SDKSessionID, - AnalysisInfo *AnalysisInformation, unsigned short int ReferenceSource, char ReferencePath[], - char UserProfile_Description[], LVBoolean *ReferenceChanged, - long *ErrorCode); - -void __cdecl NewUserProfile(SDK_Reference *SDKSessionID, char CameraSNPhasics[], - char ProfileName[], char UserProfileDirectory[], char ProfilePathFileOut[], - long pathFileOut_bufSize, long *ErrorCode); - -void __cdecl SaveCurrentUserProfile(SDK_Reference *SDKSessionID, - long *ErrorCode); - -void __cdecl SaveMaskDescriptor(SDK_Reference *SDKSessionID, char MaskFile[], - long ROI_GlobalRectangle[], long globalRect_bufSize, - unsigned short int ROI_NbOfContours, unsigned long ROI_Contours_info[], - long contoursInfo_bufSize, long ROI_Contours_coordinates[], - long contoursCoord_bufSize, long *ErrorCode); - - -// Camera control functions -void __cdecl StartLiveMode(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl StopLiveMode(SDK_Reference *SDKSessionID, long *ErrorCodeID); - -void __cdecl CameraInit(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraStart(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraStop(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraClose(SDK_Reference *SDKSessionID, long *ErrorCode); - -void __cdecl CameraList(SDK_Reference *SDKSessionID, char CameraList_SNPhasics[], - long camList_bufSize, long *ErrorCode); - -void __cdecl CameraSetup(SDK_Reference *SDKSessionID, unsigned short int CameraParameter, - unsigned long Value, long *ErrorCode); - -void __cdecl Camera_ConvertExposureMs(SDK_Reference *SDKSessionID, - double ExposureRawValueIn, double *ExposureValueMsOut, long *ErrorCode); -void __cdecl Camera_GetNumberOfAttribute(SDK_Reference *SDKSessionID, - long *NumberOfAttribute, long *ErrorCode); - -void __cdecl Camera_GetAttribute(SDK_Reference *SDKSessionID, - unsigned short int AttributeID, double *AttributeValueOut, long *ErrorCode); -void __cdecl Camera_SetAttribute(SDK_Reference *SDKSessionID, - unsigned short int AttributeID, double *AttributeValue, long *ErrorCode); - -void __cdecl Camera_GetAttributeList(SDK_Reference *SDKSessionID, - unsigned short int AttributeID[], long attribID_bufSize, - char AttributeName_SeparatedByTab[], long attribName_bufSize, - long AttributeGmin[], long attribGmin_bufSize, long AttributeGmax[], - long attribGmax_bufSize, long *ErrorCode); - -// Interferogram analysis functions -void __cdecl ArrayAnalysis(SDK_Reference *SDKSessionID, - short int InterferogramInArrayI16[], long Interfero_bufSize, - float Intensity[], long Intensity_bufSize, float Phase[], - long Phase_bufSize, TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, - ArraySize *ImageCameraSize, long *ErrorCode); - -void __cdecl FileAnalysis(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, - char InterferogramFile[], float Intensity[], long Intensity_bufSize, - float Phase[], long Phase_bufSize, TiltInfo *TiltInformation, - long *ErrorCode); - -void __cdecl GrabLiveMode(SDK_Reference *SDKSessionID, float Phase[], - long Phase_bufSize, float Intensity[], long Intensity_bufSize, - TiltInfo *TiltInformation, ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl GrabImage(SDK_Reference *SDKSessionID, short int Image[], - long Image_bufSize, ArraySize *ImageCameraSize, long *ErrorCode); - -void __cdecl Snap(SDK_Reference *SDKSessionID, float Phase[], - long Phase_bufSize, float Intensity[], long Intensity_bufSize, - TiltInfo *TiltInformation, long *ErrorCode); - -void __cdecl GrabToFile(SDK_Reference *SDKSessionID, unsigned long PaletteNumber, - char InterferogramFile[], LVBoolean *CheckOverWrite, long *ErrorCode); - -void __cdecl GetPhaseGradients(SDK_Reference *SDKSessionID, - ArraySize *AnalysisArraySize, float GradientX[], long GradX_bufSize, - float GradientY[], long GradY_bufSize, long *ErrorCode); - -void __cdecl SetIntegrationParam(SDK_Reference *SDKSessionID, - unsigned char Adv_Activation, unsigned short int Adv_Niter, float Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl GetQualityMap(SDK_Reference *SDKSessionID, ArraySize *AnalysisArraySize, - float QualityMap[], long qualityMap_bufSize, long *ErrorCode); - -void __cdecl GetIntegrationParam(SDK_Reference *SDKSessionID, - unsigned char *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl SetUnwrapParam(SDK_Reference *SDKSessionID, - unsigned short int UnwrappingAlgorithm, unsigned char UnwrappingOptions[], - long unwrapOptions_bufSize, long *ErrorCode); - -void __cdecl GetUnwrapParam(SDK_Reference *SDKSessionID, - unsigned short int *UnwrappingAlgoritm, unsigned char UnwrappingOptions[], - long unwrapOptions_bufSize, long *ErrorCode); - -void __cdecl getIntegrationParamOut(SDK_Reference *SDKSessionID, - LVBoolean *Adv_Activation, unsigned short int *Adv_Niter, float *Adv_MSE_Threshold, - long *ErrorCode); - -void __cdecl ADVTR_GetAnalysisArraySize(SDK_Reference *SDKSessionID, - double TR_AnalyseIn, ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl ADVTR_ComputeAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, - short int InterferogramI16[], long interfero_bufSize, double *TR_AnalyseOut, - long *ErrorCode); - -void __cdecl ADVTR_ArrayAnalysisTr(SDK_Reference *SDKSessionID, ArraySize *ImageSize, - short int InterferogramI16[], long interfero_bufSize, double TR_AnalyseIn, - ArraySize *AnalysisArraySize, float Phase[], long phase_bufSize, - float Intensity[], long intensity_bufSize, TiltInfo *TiltInformation, - long *ErrorCode); - -void __cdecl GetImageInfo(SDK_Reference *SDKSessionID, char InterferogramFile[], - ArraySize *ImageSize, long *ErrorCode); - - -// Input-Output functions -void __cdecl LoadInterferogram(SDK_Reference *SDKSessionID, - char InterferogramFile[], ArraySize *ImageSize, short int InterferogramI16[], - long interfero_bufSize, long *ErrorCode); - -void __cdecl LoadMeasurementInfo(SDK_Reference *SDKSessionID, char PhaseFile[], - ArraySize *AnalysisArraySize, long *ErrorCode); - -void __cdecl LoadMeasurement(SDK_Reference *SDKSessionID, char PhaseFile[], - ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, - float Intensity[], long Intensity_bufSize, long *ErrorCode); - -void __cdecl SaveLastMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], - unsigned short int MeasurementList[], long measurementList_bufSize, - char MeasurementFilesOut[], long filesOut_bufSize, long *ErrorCode); - -void __cdecl SaveMeasurement(SDK_Reference *SDKSessionID, char GenericPath[], - ArraySize *AnalysisArraySize, float Phase[], long Phase_bufSize, - float Intensity[], long Intensity_bufSize, char PhaseFileOut[], - long phaseFileOut_bufSize, char IntensityFileOut[], - long intensityFileOut_bufSize, long *ErrorCode); - - -long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module); - - -//****************************************************************// -// Definitions of structures used in the Zernike_SDK functions // -//****************************************************************// - - -// Zernike Information structure giving on information on the projection base and the number of polynomials. -typedef struct { - unsigned short int Base; - long Polynomials; - } ZernikeInformation; - -//Zernike Parameters giving usefull information for the Zernike calculation -typedef struct { - unsigned long ImageRowSize; - unsigned long ImageColSize; - unsigned long MaskRowSize; - unsigned long MaskColSize; - unsigned short int Base; - } ZernikeParam; - -// Projection Basis Parameters, used with MET_GetProjectionBasisParameters -// defines the position and dimension of the pupil in the image array, -// as well as, the Basis type: Zernike or Legendre -typedef struct { - unsigned long PupilSize_Height; - unsigned long PupilSize_Width; - float PupilPosition_CH; - float PupilPosition_CW; - unsigned long ImageSize_Height; - unsigned long ImageSize_Width; - unsigned short Basis; - } ZKLParam; - -//**************************************************************// -// Zernike_SDK Basic functions // -//**************************************************************// - -/*! - * Zernike_PhaseFiltering - */ -void __cdecl Zernike_PhaseFiltering(float PhaseMap[], long phase_bufSize, - ArraySize *PhaseMapArraySize, double ProjectionCoefficients[], - long projCoef_bufSize, unsigned char PolyListToFilter[], - long polyList_bufSize, unsigned short int Filtering_Option, long *ErrorCode); -/*! - * Zernike_GetZernikeInfo - */ -void __cdecl Zernike_GetZernikeInfo(ZernikeInformation *ZernikeInfo, ArraySize *PhaseMapArraySize, - char ZernikeVersion[], long version_bufSize); -/*! - * Zernike_PhaseProjection - */ -void __cdecl Zernike_PhaseProjection(float PhaseMap[], long phase_bufSize, - ArraySize *PhaseMapArraySize, double ProjectionCoefficientsIn[], - long projCoef_bufSize, long *ErrorCode); -/*! - * Zernike_GetProjectionSet - */ -void __cdecl Zernike_GetProjectionSet(float ProjectionSetIn[], - long ZLprojSet_bufSize, ZernikeInformation *ZernikeInfo, ArraySize *PhaseMapArraySize, - long *ErrorCode); -/*! - * Zernike_UpdateProjection_fromUserProfile - */ -long __cdecl Zernike_UpdateProjection_fromUserProfile( - char UserProfileDirectory[], unsigned char PolynomialOrder, long *ErrorCode); -/*! - * Zernike_UpdateProjection_fromPhaseFile - */ -long __cdecl Zernike_UpdateProjection_fromPhaseFile(char PhaseFile[], - unsigned char PolynomialOrder, long *ErrorCode); -/*! - * Zernike_UpdateProjection_fromParameter - */ -long __cdecl Zernike_UpdateProjection_fromParameter(ZernikeParam *ZernkeParameters, - unsigned char PolynomialOrder, long *ErrorCode); - -/*! - * Zernike_UpdateProjection_fromParameters2 - * This function computes the Projection Basis (Zernike or Legendre) according - * the input parameters which are: - * - Dimension & Position of the analysis pupil - * - Dimension of the image that will contain the analysis pupil - * - Choice of the Basis that will be computed (Zernike or Legendre) - * - * If the input "Mask_Obstruction" array is not empty, the program will used - * it for computing the Projection Basis. In this case, the dimension of the - * "Mask_Obstruction" array should be identic to the image dimension specified - * in the ProjectionBasis_Parameters. - */ -void __cdecl Zernike_UpdateProjection_fromParameters2( - ZKLParam *ProjectionBasis_Parameters, float Mask_Obstruction[], - long mask_bufSize, ArraySize *MaskArraySize, unsigned char PolynomialsOrder, - long *ErrorCode); - -/*! - * Zernike_FreeMemory - */ -long __cdecl Zernike_FreeMemory(long *ErrorCode); -/*! - * Zernike_GetPolynomialsList - */ -void __cdecl Zernike_GetPolynomialsList(char PlynomialsList[], - long polyList_bufSize); - - -#pragma pack(pop) diff --git a/microscope/wavefront_sensors/__init__.py b/microscope/wavefront_sensors/__init__.py deleted file mode 100644 index e69de29b..00000000 From 0b4e68a4980e262268a833253a8348ff0505928c Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:12:47 +0200 Subject: [PATCH 22/27] six is not imported while it is used once for encoding a string. I replaced that for a encode call --- microscope/lasers/obis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microscope/lasers/obis.py b/microscope/lasers/obis.py index 6b08c7c6..97a9c0ac 100644 --- a/microscope/lasers/obis.py +++ b/microscope/lasers/obis.py @@ -56,7 +56,7 @@ def __init__(self, com, baud, timeout, *args, **kwargs): # on/off remotely. self._write(b'SYSTem:AUTostart?') response = self._readline() - self._logger.info('Response to Autostart: [%s]' % response) + self._logger.info('Response to Autostart: [%s]' % response.decode()) def _write(self, command): """Send a command.""" @@ -69,7 +69,7 @@ def _readline(self): """ response = self.connection.readline().strip() if self.connection.readline().strip() != b'OK': - print('Did not get a proper answer from the laser serial comm.') + raise Exception('Did not get a proper answer from the laser serial comm.') return response def _flush_handshake(self): From b3cd55da0a65a100a4ac1655ebcd25ab6838a141 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:20:46 +0200 Subject: [PATCH 23/27] added supported device --- doc/supported-devices.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/supported-devices.rst b/doc/supported-devices.rst index ee390fe8..4ebc1f2d 100644 --- a/doc/supported-devices.rst +++ b/doc/supported-devices.rst @@ -32,4 +32,5 @@ Lasers - Cobolt (:class:`microscope.lasers.cobolt`) - Coherent Sapphire (:class:`microscope.lasers.saphhire`) +- Coherent OBIS (:class:`microscope.lasers.obis`) - Omicron Deepstar (:class:`microscope.lasers.deepstar`) From c8c673401e04e0a5b851b3c7109a1c5ec22b531b Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:34:47 +0200 Subject: [PATCH 24/27] Obis clean branch --- NEWS | 2 +- microscope/cameras/SDK3.py | 23 ++-- microscope/cameras/SDK3Cam.py | 2 +- microscope/cameras/andorsdk3.py | 3 + microscope/devices.py | 16 +-- microscope/lasers/sapphire.py | 10 +- microscope/testsuite/deviceserver_test.py | 2 +- microscope/testsuite/test_devices.py | 127 ---------------------- 8 files changed, 19 insertions(+), 166 deletions(-) diff --git a/NEWS b/NEWS index b2424dee..c751532d 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,7 @@ The following is a summary of the user-visible changes for each of python-microscope releases. Version 0.2.0 (2018/06/13) --------------------------- +------------------------- * New classes: diff --git a/microscope/cameras/SDK3.py b/microscope/cameras/SDK3.py index f85a2a4e..eeaafe90 100644 --- a/microscope/cameras/SDK3.py +++ b/microscope/cameras/SDK3.py @@ -21,11 +21,9 @@ ################## import ctypes -import platform +import os from ctypes import POINTER, c_int, c_uint, c_double, c_void_p -arch, plat = platform.architecture() - #### typedefs AT_H = ctypes.c_int AT_BOOL = ctypes.c_int @@ -35,17 +33,13 @@ _stdcall_libraries = {} -if plat.startswith('Windows'): - if arch == '32bit': - _stdcall_libraries['ATCORE'] = ctypes.WinDLL('atcore') - _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('atutility') - else: - _stdcall_libraries['ATCORE'] = ctypes.OleDLL('atcore') - _stdcall_libraries['ATUTIL'] = ctypes.OleDLL('atutility') +if os.name in ('nt', 'ce'): + _stdcall_libraries['ATCORE'] = ctypes.WinDLL('atcore') + _stdcall_libraries['ATUTIL'] = ctypes.WinDLL('atutility') CALLBACKTYPE = ctypes.WINFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p) else: - _stdcall_libraries['ATCORE'] = ctypes.CDLL('libatcore.so') - _stdcall_libraries['ATUTIL'] = ctypes.CDLL('libatutility.so') + _stdcall_libraries['ATCORE'] = ctypes.CDLL('atcore.so') + _stdcall_libraries['ATUTIL'] = ctypes.CDLL('atutility.so') CALLBACKTYPE = ctypes.CFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p) #### Defines @@ -98,7 +92,6 @@ def errCode(name,value): errCode('AT_ERR_NULL_WAIT_PTR', 35) errCode('AT_ERR_NULL_PTRSIZE', 36) errCode('AT_ERR_NOMEMORY', 37) -errCode('AT_ERR_DEVICEINUSE', 38) errCode('AT_ERR_HARDWARE_OVERFLOW', 100) @@ -210,8 +203,10 @@ def __call__(self, *args): ret.append(r) #print r, r._type_ + #print ars res = self.f(*ars) - + #print res + if not res == AT_SUCCESS: if res == AT_ERR_TIMEDOUT or res == AT_ERR_NODATA: #handle timeouts as a special case, as we expect to get them diff --git a/microscope/cameras/SDK3Cam.py b/microscope/cameras/SDK3Cam.py index 878f9bb8..1b58ac2c 100644 --- a/microscope/cameras/SDK3Cam.py +++ b/microscope/cameras/SDK3Cam.py @@ -22,7 +22,7 @@ ################ -from . import SDK3 +from .import SDK3 class ATProperty(object): def connect(self, handle, propertyName): diff --git a/microscope/cameras/andorsdk3.py b/microscope/cameras/andorsdk3.py index bb4cb2e9..dad1d120 100644 --- a/microscope/cameras/andorsdk3.py +++ b/microscope/cameras/andorsdk3.py @@ -27,6 +27,7 @@ import time from six.moves import queue + from .SDK3Cam import * # SDK data pointer type @@ -102,6 +103,8 @@ def __init__(self, *args, **kwargs): SDK3.InitialiseLibrary() self._index = kwargs.get('index', 0) self.handle = None + #self._sdk3cam = SDK3Camera(self._index) + #SDK3Camera.__init__(self, self._index) self.add_setting('use_callback', 'bool', lambda: self._using_callback, self._enable_callback, diff --git a/microscope/devices.py b/microscope/devices.py index 74493bd4..6292925f 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -292,15 +292,15 @@ def update_settings(self, incoming, init=False): results[key] = NotImplemented update_keys.remove(key) continue - if _call_if_callable(self.settings[key]['readonly']): + if self.settings[key]['readonly']: continue self.settings[key]['set'](incoming[key]) - # Read back values in second loop. for key in update_keys: results[key] = self.settings[key]['get']() return results + def keep_acquiring(func): """Wrapper to preserve acquiring state of data capture devices.""" def wrapper(self, *args, **kwargs): @@ -940,17 +940,7 @@ def _set_power_mw(self, mw): @Pyro4.expose def set_power_mw(self, mw): - """Set the power from an argument in mW and save the set point. - - Args: - mw (float): Power in mW. Value will be clipped to the - valid range for the laser. See the methods - :func:`get_max_power_mw` and :func:`get_min_power_mw` - to retrieve the valid range. - - Returns: - void - """ + """Set the power from an argument in mW and save the set point.""" self._set_point = mw self._set_power_mw(mw) diff --git a/microscope/lasers/sapphire.py b/microscope/lasers/sapphire.py index 4a67f864..cec5bab8 100644 --- a/microscope/lasers/sapphire.py +++ b/microscope/lasers/sapphire.py @@ -52,14 +52,6 @@ def __init__(self, com=None, baud=19200, timeout=0.5, *args, **kwargs): headID = int(float(self.send(b'?hid'))) self._logger.info("Sapphire laser serial number: [%s]" % headID) - def _write(self, command): - count = super(SapphireLaser, self)._write(command) - ## This device always writes backs something. If echo is on, - ## it's the whole command, otherwise just an empty line. Read - ## it and throw it away. - self._readline() - return count - def send(self, command): """Send command and retrieve response.""" self._write(command) @@ -123,7 +115,7 @@ def initialize(self): def enable(self): self._logger.info("Turning laser ON.") # Turn on emission. - response = self.send(b'l=1') + response = self._write(b'l=1') self._logger.info("l=1: [%s]" % response.decode()) # Enabling laser might take more than 500ms (default timeout) diff --git a/microscope/testsuite/deviceserver_test.py b/microscope/testsuite/deviceserver_test.py index 6ecf1211..f8e7da64 100644 --- a/microscope/testsuite/deviceserver_test.py +++ b/microscope/testsuite/deviceserver_test.py @@ -40,7 +40,7 @@ class BaseTestServeDevices(unittest.TestCase): p (multiprocessing.Process): device server process. """ DEVICES = [] - TIMEOUT = 5 + TIMEOUT = 2 def setUp(self): init = microscope.deviceserver.serve_devices self.p = multiprocessing.Process(target=init, args=(self.DEVICES,)) diff --git a/microscope/testsuite/test_devices.py b/microscope/testsuite/test_devices.py index 359682b3..9d340579 100644 --- a/microscope/testsuite/test_devices.py +++ b/microscope/testsuite/test_devices.py @@ -19,15 +19,11 @@ ## along with Microscope. If not, see . import unittest -import unittest.mock import numpy -import serial import six import microscope.testsuite.devices as dummies -import microscope.testsuite.mock_devices as mocks - class TestDeformableMirror(unittest.TestCase): def setUp(self): @@ -82,128 +78,5 @@ def test_validate_pattern(self): self.dm.apply_pattern(patterns) -class TestSerialMock(unittest.TestCase): - ## Our tests for serial devices depend on our SerialMock base class - ## working properly so yeah, we need tests for that too. - class Serial(mocks.SerialMock): - eol = b'\r\n' - def handle(self, command): - if command.startswith(b'echo '): - self.in_buffer.write(command[5:] + self.eol) - elif command in [b'foo', b'bar']: - pass - else: - raise RuntimeError("unknown command '%s'" % command.decode()) - - def setUp(self): - self.serial = TestSerialMock.Serial() - patcher = unittest.mock.patch.object(TestSerialMock.Serial, 'handle', - wraps=self.serial.handle) - self.addCleanup(patcher.stop) - self.mock = patcher.start() - - def test_simple_commands(self): - self.serial.write(b'foo\r\n') - self.mock.assert_called_once_with(b'foo') - - def test_partial_commands(self): - self.serial.write(b'fo') - self.serial.write(b'o') - self.serial.write(b'\r\n') - self.serial.handle.assert_called_once_with(b'foo') - - def test_multiple_commands(self): - self.serial.write(b'foo\r\nbar\r\n') - calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] - self.assertEqual(self.serial.handle.mock_calls, calls) - - def test_unix_eol(self): - self.serial.eol = b'\n' - self.serial.write(b'foo\nbar\n') - calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] - self.assertEqual(self.serial.handle.mock_calls, calls) - - def test_write(self): - self.serial.write(b'echo qux\r\n') - self.assertEqual(self.serial.readline(), b'qux\r\n') - - -class LaserTests: - """Base class for :class:`LaserDevice` tests. - - This class implements all the general laser tests and is meant to be - mixed with :class:`unittest.TestCase`. The subclass must implement - the `setUp` method which must add two properties: - - `device` - Instance of the :class:`LaserDevice` implementation being tested. - - `laser` - Object with a multiple attributes that specify the hardware and - control the tests, such as the device max and min power values. - Such attributes may as well be attributes in the class that mocks - the hardware. - """ - def __init__(self): - self.laser = None - self.device = None - - def test_connection_defaults(self): - self.assertEqual(self.laser.connection.baudrate, self.device.baudrate) - self.assertEqual(self.laser.connection.parity, self.device.parity) - self.assertEqual(self.laser.connection.bytesize, self.device.bytesize) - self.assertEqual(self.laser.connection.stopbits, self.device.stopbits) - self.assertEqual(self.laser.connection.rtscts, self.device.rtscts) - self.assertEqual(self.laser.connection.dsrdtr, self.device.dsrdtr) - - def test_being(self): - self.assertTrue(self.laser.is_alive()) - - def test_turning_on_and_off(self): - self.assertTrue(self.laser.get_is_on()) - self.laser.disable() - self.assertFalse(self.laser.get_is_on()) - self.laser.enable() - self.assertTrue(self.laser.get_is_on()) - - def test_query_power_range(self): - min_mw = self.laser.get_min_power_mw() - max_mw = self.laser.get_max_power_mw() - self.assertIsInstance(min_mw, float) - self.assertIsInstance(max_mw, float) - self.assertEqual(round(min_mw), self.device.min_power) - self.assertEqual(round(max_mw), self.device.max_power) - - def test_setting_power(self): - power = self.laser.get_power_mw() - self.assertIsInstance(power, float) - self.assertEqual(round(power), self.device.default_power) - - new_power = (self.device.min_power - + ((self.device.max_power - self.device.min_power) /2.0)) - self.laser.set_power_mw(new_power) - self.assertEqual(round(self.laser.get_power_mw()), round(new_power)) - - def test_setting_power_outside_limit(self): - below_limit = self.device.min_power - 10.0 - above_limit = self.device.max_power + 10.0 - self.laser.set_power_mw(below_limit) - self.assertEqual(self.laser.get_power_mw(), self.laser.get_min_power_mw(), - 'clip setting power to the valid range') - self.laser.set_power_mw(above_limit) - self.assertEqual(self.laser.get_power_mw(), self.laser.get_max_power_mw(), - 'clip setting power to the valid range') - - -class TestCoherentSapphireLaser(unittest.TestCase, LaserTests): - def setUp(self): - from microscope.lasers.sapphire import SapphireLaser - from microscope.testsuite.mock_devices import CoherentSapphireLaserMock - with unittest.mock.patch('microscope.lasers.sapphire.serial.Serial', - new=CoherentSapphireLaserMock): - self.laser = SapphireLaser('/dev/null') - self.device = CoherentSapphireLaserMock - - if __name__ == '__main__': unittest.main() From ff37e5318e81a6bb4708d8378c60937c3f6524ef Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:40:09 +0200 Subject: [PATCH 25/27] Obis clean branch --- doc/example.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/example.py diff --git a/doc/example.py b/doc/example.py deleted file mode 100644 index e69de29b..00000000 From b97a86888ff572bdbd9054487356357b9175b2a1 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:47:43 +0200 Subject: [PATCH 26/27] Obis clean branch --- microscope/cameras/andorsdk3.py | 3 - microscope/lasers/sapphire.py | 10 ++- microscope/testsuite/test_devices.py | 127 +++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 4 deletions(-) diff --git a/microscope/cameras/andorsdk3.py b/microscope/cameras/andorsdk3.py index dad1d120..bb4cb2e9 100644 --- a/microscope/cameras/andorsdk3.py +++ b/microscope/cameras/andorsdk3.py @@ -27,7 +27,6 @@ import time from six.moves import queue - from .SDK3Cam import * # SDK data pointer type @@ -103,8 +102,6 @@ def __init__(self, *args, **kwargs): SDK3.InitialiseLibrary() self._index = kwargs.get('index', 0) self.handle = None - #self._sdk3cam = SDK3Camera(self._index) - #SDK3Camera.__init__(self, self._index) self.add_setting('use_callback', 'bool', lambda: self._using_callback, self._enable_callback, diff --git a/microscope/lasers/sapphire.py b/microscope/lasers/sapphire.py index cec5bab8..4a67f864 100644 --- a/microscope/lasers/sapphire.py +++ b/microscope/lasers/sapphire.py @@ -52,6 +52,14 @@ def __init__(self, com=None, baud=19200, timeout=0.5, *args, **kwargs): headID = int(float(self.send(b'?hid'))) self._logger.info("Sapphire laser serial number: [%s]" % headID) + def _write(self, command): + count = super(SapphireLaser, self)._write(command) + ## This device always writes backs something. If echo is on, + ## it's the whole command, otherwise just an empty line. Read + ## it and throw it away. + self._readline() + return count + def send(self, command): """Send command and retrieve response.""" self._write(command) @@ -115,7 +123,7 @@ def initialize(self): def enable(self): self._logger.info("Turning laser ON.") # Turn on emission. - response = self._write(b'l=1') + response = self.send(b'l=1') self._logger.info("l=1: [%s]" % response.decode()) # Enabling laser might take more than 500ms (default timeout) diff --git a/microscope/testsuite/test_devices.py b/microscope/testsuite/test_devices.py index 9d340579..359682b3 100644 --- a/microscope/testsuite/test_devices.py +++ b/microscope/testsuite/test_devices.py @@ -19,11 +19,15 @@ ## along with Microscope. If not, see . import unittest +import unittest.mock import numpy +import serial import six import microscope.testsuite.devices as dummies +import microscope.testsuite.mock_devices as mocks + class TestDeformableMirror(unittest.TestCase): def setUp(self): @@ -78,5 +82,128 @@ def test_validate_pattern(self): self.dm.apply_pattern(patterns) +class TestSerialMock(unittest.TestCase): + ## Our tests for serial devices depend on our SerialMock base class + ## working properly so yeah, we need tests for that too. + class Serial(mocks.SerialMock): + eol = b'\r\n' + def handle(self, command): + if command.startswith(b'echo '): + self.in_buffer.write(command[5:] + self.eol) + elif command in [b'foo', b'bar']: + pass + else: + raise RuntimeError("unknown command '%s'" % command.decode()) + + def setUp(self): + self.serial = TestSerialMock.Serial() + patcher = unittest.mock.patch.object(TestSerialMock.Serial, 'handle', + wraps=self.serial.handle) + self.addCleanup(patcher.stop) + self.mock = patcher.start() + + def test_simple_commands(self): + self.serial.write(b'foo\r\n') + self.mock.assert_called_once_with(b'foo') + + def test_partial_commands(self): + self.serial.write(b'fo') + self.serial.write(b'o') + self.serial.write(b'\r\n') + self.serial.handle.assert_called_once_with(b'foo') + + def test_multiple_commands(self): + self.serial.write(b'foo\r\nbar\r\n') + calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] + self.assertEqual(self.serial.handle.mock_calls, calls) + + def test_unix_eol(self): + self.serial.eol = b'\n' + self.serial.write(b'foo\nbar\n') + calls = [unittest.mock.call(x) for x in [b'foo', b'bar']] + self.assertEqual(self.serial.handle.mock_calls, calls) + + def test_write(self): + self.serial.write(b'echo qux\r\n') + self.assertEqual(self.serial.readline(), b'qux\r\n') + + +class LaserTests: + """Base class for :class:`LaserDevice` tests. + + This class implements all the general laser tests and is meant to be + mixed with :class:`unittest.TestCase`. The subclass must implement + the `setUp` method which must add two properties: + + `device` + Instance of the :class:`LaserDevice` implementation being tested. + + `laser` + Object with a multiple attributes that specify the hardware and + control the tests, such as the device max and min power values. + Such attributes may as well be attributes in the class that mocks + the hardware. + """ + def __init__(self): + self.laser = None + self.device = None + + def test_connection_defaults(self): + self.assertEqual(self.laser.connection.baudrate, self.device.baudrate) + self.assertEqual(self.laser.connection.parity, self.device.parity) + self.assertEqual(self.laser.connection.bytesize, self.device.bytesize) + self.assertEqual(self.laser.connection.stopbits, self.device.stopbits) + self.assertEqual(self.laser.connection.rtscts, self.device.rtscts) + self.assertEqual(self.laser.connection.dsrdtr, self.device.dsrdtr) + + def test_being(self): + self.assertTrue(self.laser.is_alive()) + + def test_turning_on_and_off(self): + self.assertTrue(self.laser.get_is_on()) + self.laser.disable() + self.assertFalse(self.laser.get_is_on()) + self.laser.enable() + self.assertTrue(self.laser.get_is_on()) + + def test_query_power_range(self): + min_mw = self.laser.get_min_power_mw() + max_mw = self.laser.get_max_power_mw() + self.assertIsInstance(min_mw, float) + self.assertIsInstance(max_mw, float) + self.assertEqual(round(min_mw), self.device.min_power) + self.assertEqual(round(max_mw), self.device.max_power) + + def test_setting_power(self): + power = self.laser.get_power_mw() + self.assertIsInstance(power, float) + self.assertEqual(round(power), self.device.default_power) + + new_power = (self.device.min_power + + ((self.device.max_power - self.device.min_power) /2.0)) + self.laser.set_power_mw(new_power) + self.assertEqual(round(self.laser.get_power_mw()), round(new_power)) + + def test_setting_power_outside_limit(self): + below_limit = self.device.min_power - 10.0 + above_limit = self.device.max_power + 10.0 + self.laser.set_power_mw(below_limit) + self.assertEqual(self.laser.get_power_mw(), self.laser.get_min_power_mw(), + 'clip setting power to the valid range') + self.laser.set_power_mw(above_limit) + self.assertEqual(self.laser.get_power_mw(), self.laser.get_max_power_mw(), + 'clip setting power to the valid range') + + +class TestCoherentSapphireLaser(unittest.TestCase, LaserTests): + def setUp(self): + from microscope.lasers.sapphire import SapphireLaser + from microscope.testsuite.mock_devices import CoherentSapphireLaserMock + with unittest.mock.patch('microscope.lasers.sapphire.serial.Serial', + new=CoherentSapphireLaserMock): + self.laser = SapphireLaser('/dev/null') + self.device = CoherentSapphireLaserMock + + if __name__ == '__main__': unittest.main() From ab02deddfe7c89b849bc0300a565a65c69383a38 Mon Sep 17 00:00:00 2001 From: Julio Mateos Langerak Date: Thu, 2 Aug 2018 14:51:13 +0200 Subject: [PATCH 27/27] Obis clean branch --- NEWS | 2 +- microscope/devices.py | 12 +++++++++++- microscope/testsuite/deviceserver_test.py | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index c751532d..b2424dee 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,7 @@ The following is a summary of the user-visible changes for each of python-microscope releases. Version 0.2.0 (2018/06/13) -------------------------- +-------------------------- * New classes: diff --git a/microscope/devices.py b/microscope/devices.py index 6292925f..3c01f6c4 100644 --- a/microscope/devices.py +++ b/microscope/devices.py @@ -940,7 +940,17 @@ def _set_power_mw(self, mw): @Pyro4.expose def set_power_mw(self, mw): - """Set the power from an argument in mW and save the set point.""" + """Set the power from an argument in mW and save the set point. + + Args: + mw (float): Power in mW. Value will be clipped to the + valid range for the laser. See the methods + :func:`get_max_power_mw` and :func:`get_min_power_mw` + to retrieve the valid range. + + Returns: + void + """ self._set_point = mw self._set_power_mw(mw) diff --git a/microscope/testsuite/deviceserver_test.py b/microscope/testsuite/deviceserver_test.py index f8e7da64..6ecf1211 100644 --- a/microscope/testsuite/deviceserver_test.py +++ b/microscope/testsuite/deviceserver_test.py @@ -40,7 +40,7 @@ class BaseTestServeDevices(unittest.TestCase): p (multiprocessing.Process): device server process. """ DEVICES = [] - TIMEOUT = 2 + TIMEOUT = 5 def setUp(self): init = microscope.deviceserver.serve_devices self.p = multiprocessing.Process(target=init, args=(self.DEVICES,))