From 7bb30d901e0cc201564e200bd6dfa8dd3b07093d Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 7 Apr 2020 18:50:24 +0200 Subject: [PATCH 01/49] Spectro master branch Base --- gui/PLspectrum/PL_spectrum_gui.py | 208 ++++++ gui/PLspectrum/ui_hirondelle200.ui | 702 ++++++++++++++++++ hardware/spectrometer/shamrock.py | 717 +++++++++++++++++++ interface/spectrometer_complete_interface.py | 257 +++++++ logic/spectrum_logic.py | 574 +++++++++++++++ 5 files changed, 2458 insertions(+) create mode 100644 gui/PLspectrum/PL_spectrum_gui.py create mode 100644 gui/PLspectrum/ui_hirondelle200.ui create mode 100644 hardware/spectrometer/shamrock.py create mode 100644 interface/spectrometer_complete_interface.py create mode 100644 logic/spectrum_logic.py diff --git a/gui/PLspectrum/PL_spectrum_gui.py b/gui/PLspectrum/PL_spectrum_gui.py new file mode 100644 index 0000000000..a6dd475bad --- /dev/null +++ b/gui/PLspectrum/PL_spectrum_gui.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +""" +This module contains a GUI for operating the spectrum logic module. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +import os +import pyqtgraph as pg +import numpy as np + +from core.connector import Connector +from core.statusvariable import StatusVar +from core.util import units + +from gui.colordefs import QudiPalettePale as palette +from gui.guibase import GUIBase +from qtpy import QtCore +from qtpy import QtWidgets +from qtpy import uic + + +class MainWindow(QtWidgets.QMainWindow): + + def __init__(self): + """ Create the laser scanner window. + """ + # Get the path to the *.ui file + this_dir = os.path.dirname(__file__) + ui_file = os.path.join(this_dir, 'ui_PLspectrum.ui') + + # Load it + super().__init__() + uic.loadUi(ui_file, self) + self.show() + + +class PLspectrumGUI(GUIBase): + """ + """ + + # declare connectors + spectrumlogic = Connector(interface='SpectrumLogic') + + def __init__(self, config, **kwargs): + super().__init__(config=config, **kwargs) + + def on_activate(self): + """ Definition and initialisation of the GUI. + """ + + # connect the logic module from the declared connector + self._spectrum_logic = self.spectrumlogic() + + # setting up the window + self._mw = MainWindow() + self._mw.centralwidget.hide() + self._mw.setDockNestingEnabled(True) + + + # giving the plots names allows us to link their axes together + self._spec = self._mw.spectrumPlot + self._img = self._mw.imagePlot + self._spec_item = self._spec.plotItem + self._img_item = self._img.ImageItem + + # create a new ViewBox, link the right axis to its coordinate system + self._right_axis = pg.ViewBox() # Create a ViewBox right axis + self._spec_item.showAxis('right') # Show the right axis of plotItem + self._spec_item.scene().addItem(self._right_axis) # associate the ViewBox right axis to the plotItem + self._spec_item.getAxis('right').linkToView(self._right_axis) # link this right axis to the ViewBox + self._right_axis.setXLink(self._spec_item) # link the ViewBox object to the plotItem x axis + + # create a new ViewBox, link the top axis to its coordinate system (same procedure) + self._top_axis = pg.ViewBox() + self._spec_item.showAxis('top') + self._spec_item.scene().addItem(self._top_axis) + self._spec_item.getAxis('top').linkToView(self._top_axis) + self._top_axis.setYLink(self._spec_item) + self._top_axis.invertX(b=True) # We force the x axis to be rightward + + # label plot axis : + + self._spec.setLabel('left', 'Fluorescence', units='counts/s') + self._spec.setLabel('right', 'Number of Points', units='#') + self._spec.setLabel('bottom', 'Wavelength', units='m') + self._spec.setLabel('top', 'Relative Frequency', units='Hz') + + # Create 2 empty plot curve to be filled later, set its pen (curve style) + self._curve1 = self._spec.plot() + self._curve1.setPen(palette.c1, width=2) + + # Connect signals : + # Action (spectro): + self._mw.actionRun.triggered.connect(self.run_spectrum_acquisition) + self._mw.actionStop_Run.triggered.connect(self.stop_spectrum_acquisition) + # Button (image): + #self._mw.runImageButton.clicked.connect(self.run_image_acquisition()) + #self._mw.stopImageButton.clicked.connect(self.stop_image_acquisition()) + + self.read_settings() + + self.show() + + self._save_PNG = True + + def read_settings(self): + + self._center_wavelength = self._spectrum_logic.center_wavelength + self._detector_offset = self._spectrum_logic.detector_offset + self._grating = self._spectrum_logic.grating + self._input_slit = self._spectrum_logic.input_slit + self._input_slit_width = self._spectrum_logic.input_slit_width + self._output_slit = self._spectrum_logic.output_slit + self._output_slit_width = self._spectrum_logic.output_slit_width + self._min_wavelength, self._max_wavelength = self._spectrum_logic.wavelength_limits + + self._mw.wavelengthDSpin.setRange(self._min_wavelength, self._max_wavelength) + + # Initialize widgets slots : + self._mw.wavelengthDSpin.setValue(self._center_wavelength) + self._mw.detectorOffsetSpin.setValue(self._detector_offset) + self._mw.gratingNumCombo.setCurrentIndex(self._grating) + self._mw.inputSlitCombo.setCurrentIndex(self._input_slit-1) + self._mw.inputSlitWidthDSpin.setValue(self._input_slit_width) + self._mw.outputSlitCombo.setCurrentIndex(self._output_slit-1) + self._mw.outputSlitWidthDSpin.setValue(self._output_slit_width) + + + def update_settings(self): + + self._center_wavelength = self._mw.wavelengthDSpin.value() + self._detector_offset = self._mw.detectorOffsetSpin.value() + self._grating = self._mw.gratingNumCombo.currentIndex() + self._input_slit = self._mw.inputSlitCombo.currentIndex()+1 + self._input_slit_width = self._mw.inputSlitWidthDSpin.value() + self._output_slit = self._mw.outputSlitCombo.currentIndex()+1 + self._output_slit_width = self._mw.outputSlitWidthDSpin.value() + + self._spectrum_logic.center_wavelength = self._center_wavelength + self._spectrum_logic.detector_offset = self._detector_offset + self._spectrum_logic.grating = self._grating + self._spectrum_logic.input_slit = self._input_slit + self._spectrum_logic.input_slit_width = self._input_slit_width + self._spectrum_logic.output_slit = self._output_slit + self._spectrum_logic.output_slit_width = self._output_slit_width + + self._min_wavelength, self._max_wavelength = self._spectrum_logic.wavelength_limits + self._mw.wavelengthDSpin.setRange(self._min_wavelength, self._max_wavelength) + + def update_image_settings(self): + + self._width = 2048 # number of pixels along dispersion axis + self._height = 512 # number of pixels (perpendicular to dispersion axis) + self._pixelwidth = 13 # unit is micrometer + self._pixelheight = 13 # unit is micrometer + + def on_deactivate(self): + """ Deinitialisation performed during deactivation of the module. + """ + + self._mw.close() + + def show(self): + """Make window visible and put it above all other windows. + """ + QtWidgets.QMainWindow.show(self._mw) + self._mw.activateWindow() + self._mw.raise_() + + def run_spectrum_acquisition(self): + """Run the spectrum acquisition called from actionRun + and plot the spectrum data obtained. + """ + self.update_settings() + self._spectrum_logic.start_acquisition() + data = self._spectrum_logic.spectrum_data + wavelength = np.linspace(0, 1, len(data)) + self._curve1.setData(wavelength, data[:, 0]) + + def stop_spectrum_acquisition(self): + """Stop the spectrum acquisition called from actionStop_Run + """ + self._spectrum_logic.stop_acquisition() + + def run_image_acquisition(self): + """Run the image acquisition called from runImageButton + and plot the spectrum data obtained. + """ + self._spectrum_logic.set_read_mode('IMAGE') + self._spectrum_logic.start_acquisition() + data = self._spectrum_logic._spectrum_data + self._img_item.setImage(data) + diff --git a/gui/PLspectrum/ui_hirondelle200.ui b/gui/PLspectrum/ui_hirondelle200.ui new file mode 100644 index 0000000000..6a3747751a --- /dev/null +++ b/gui/PLspectrum/ui_hirondelle200.ui @@ -0,0 +1,702 @@ + + + Hirondelle200 + + + + 0 + 0 + 1270 + 798 + + + + Hirondelle200 + + + QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks + + + + + + 0 + 0 + 1270 + 22 + + + + + File + + + + Import Data + + + + + + + + + + + + Execute + + + + + + + + Settings + + + + + + + Display + + + + + + + Info + + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + + 62 + 38 + + + + 2 + + + + Qt::LeftToRight + + + false + + + + + 10 + 10 + 271 + 241 + + + + + + + Stop Image + + + + + + + Run image + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + QGraphicsView::AnchorUnderMouse + + + + + + + + + + + 62 + 38 + + + + Qt::LeftToRight + + + 8 + + + + + + 10 + 10 + 501 + 246 + + + + + + + + + Detector line offset : + + + + + + + Center wavelength : + + + + + + + Grating number : + + + + + + + Input slit : + + + + + + + Input slit width : + + + + + + + Output slit : + + + + + + + Output slit width : + + + + + + + + + + + + + + false + + + + + + + + 300(300) + + + + + 1200(300) + + + + + 1800(300) + + + + + + + + + Side + + + + + Front + + + + + + + + + + + + Side + + + + + Front + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 62 + 38 + + + + Qt::LeftToRight + + + QDockWidget::AllDockWidgetFeatures + + + 8 + + + + + + 10 + 10 + 411 + 325 + + + + + + + + + Read mode : + + + + + + + Number of track : + + + + + + + Tracks height : + + + + + + + Tracks offset : + + + + + + + Acquisition mode : + + + + + + + Exposure time : + + + + + + + Accumulation frequency : + + + + + + + Number of accumulated scan : + + + + + + + Repetition frequency : + + + + + + + Number of repetition : + + + + + + + + + + + + FVB + + + + + SINGLE_TRACK + + + + + MULTI_TRACK + + + + + + + + + + + + + + + + + + Single Scan (1) + + + + + Accumulate Scan (2) + + + + + Kinetic Series (3) + + + + + Live Scan (4) + + + + + Fast Kinetics (5) + + + + + + + + + + + + 3 MHz + + + + + 1 MHz + + + + + 50 kHz + + + + + + + + + + + + 3 MHz + + + + + 1 MHz + + + + + 50 kHz + + + + + + + + + + + + + + + + + 900 + 50 + + + + 1 + + + + + + 10 + 10 + 871 + 364 + + + + + 0 + 0 + + + + + 100 + 100 + + + + QGraphicsView::AnchorUnderMouse + + + + + + + + :/Images/save_logo.png:/Images/save_logo.png + + + Save + + + Save Data + + + + + + :/Images/save_logo.png:/Images/save_logo.png + + + Save As... + + + Save Data As ... + + + + + + :/Images/folder_logo.png:/Images/folder_logo.png + + + Import Data... + + + Import Spectrum Data from a File + + + + + + :/Images/settingslogo.jpg:/Images/settingslogo.jpg + + + Settings + + + Spectrometer Settings + + + + + + :/Images/runLogo.png:/Images/runLogo.png + + + Run + + + Run Spectrum Acquisition + + + + + + :/Images/doc_logo.jpg:/Images/doc_logo.jpg + + + Documentation + + + Documentation + + + + + + :/Images/stop_logo.png:/Images/stop_logo.png + + + Stop Run + + + + + Camera Settings + + + + + Spectrometer Settings + + + + + Import Spectrum + + + + + Import Background + + + + + Spectrum + + + + + Image + + + + + Documentation + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + + + +
diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py new file mode 100644 index 0000000000..5a17fa1e64 --- /dev/null +++ b/hardware/spectrometer/shamrock.py @@ -0,0 +1,717 @@ +# -*- coding: utf-8 -*- +""" +This module contains the hardware module of the Shamrock 500 +spectrometer from Andor. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +na=not applicable +""" + +from core.module import Base +from interface.spectrometer_complete_interface import SpectrometerInterface +from core.configoption import ConfigOption +from core.util.modules import get_main_dir +import os + +import numpy as np + +import ctypes as ct +import time + +ERROR_CODE = { + 20201: "SHAMROCK_COMMUNICATION_ERROR", + 20202: "SHAMROCK_SUCCESS", + 20266: "SHAMROCK_P1INVALID", + 20267: "SHAMROCK_P2INVALID", + 20268: "SHAMROCK_P3INVALID", + 20269: "SHAMROCK_P4INVALID", + 20270: "SHAMROCK_P5INVALID", + 20275: "SHAMROCK_NOT_INITIALIZED" +} + +class Shamrock(Base,SpectrometerInterface): + +# default values + _dll_location = ConfigOption('dll_location', missing='error') + + _width = 2048 #number of pixels along dispersion axis + _height = 512 #number of pixels (perpendicular to dispersion axis) + _pixelwidth = 13E-6 #unit is meter + _pixelheight = 13E-6 #unit is meter + + MAX_SLITS=4 + MAX_GRATINGS=3 + SLIT_MIN_WIDTH=10E-6 + SLIT_MAX_WIDTH=2500E-6 + +############################################################################## +# Basic functions +############################################################################## + + def on_activate(self): + """ Activate module. + """ + self.spectrometer_status = 0 + self.errorcode = self._create_errorcode() + + #self.dll = ct.cdll.LoadLibrary('C:/temp/ShamrockCIF.dll') + + self.dll = ct.cdll.LoadLibrary(self._dll_location) + + code = self.dll.ShamrockInitialize() + + if code != 20202: + self.log.info('Problem during spectrometer initialization') + self.on_deactivate() + else: + self.spectrometer_status = 1 + nd = ct.c_int() + self.dll.ShamrockGetNumberDevices(ct.byref(nd)) + #self.nd = nd.value + self.deviceID = 0 #hard coding : whatever the number of devices... we work with the first. Fix me ? + self.gratingID = self.get_grating() + self.set_number_of_pixels(self._width) + self.set_pixel_width(self._pixelwidth) + + def on_deactivate(self): + return self.dll.ShamrockClose() + + def check(self, func_val): + """ Check routine for the received error codes. + """ + + if not func_val == 20202: + self.log.error('Error in Shamrock with errorcode {0}:\n' + '{1}'.format(func_val, self.errorcode[func_val])) + return func_val + + def _create_errorcode(self): + """ Create a dictionary with the errorcode for the device. + """ + + maindir = get_main_dir() + + filename = os.path.join(maindir, 'hardware', 'spectrometer', 'errorcodes_shamrock.h') + try: + with open(filename) as f: + content = f.readlines() + except: + self.log.error('No file "errorcodes_shamrock.h" could be found in the ' + 'hardware/spectrometer directory!') + errorcode = {} + for line in content: + if '#define SHAMROCK' in line: + errorstring, errorvalue = line.split()[-2:] + errorcode[int(errorvalue)] = errorstring + + return errorcode + + def get_number_device(self): + """ + Returns the number of devices + Tested : yes + """ + number_of_devices = ct.c_int() + self.check(self.dll.ShamrockGetNumberDevices(self.deviceID, ct.byref(number_of_devices))) + return number_of_devices.value + + def get_optical_parameters(self): + """ + Returns the spectrometer optical parameters + + @return dictionnary { 'focal length (m)': float (m), + 'angular deviation (rad)': float (rad) + 'focal tilt (rad)': float (rad)} + Tested : yes + SI check : yes + + """ + focal_length = ct.c_float() + angular_deviation = ct.c_float() + focal_tilt = ct.c_float() + dico={} + self.check(self.dll.ShamrockEepromGetOpticalParams(self.deviceID, + ct.byref(focal_length), + ct.byref(angular_deviation), + ct.byref(focal_tilt))) + dico['focal length (m)'] = focal_length.value + dico['angular deviation (rad)'] = angular_deviation.value*np.pi/180 + dico['focal tilt (rad)'] = focal_tilt.value*np.pi/180 + + return dico + +############################################################################## +# Gratings functions +############################################################################## +# All functions in this section have been tested (03/04/2020) +# Comments have to be homogenized +# SI check is OK +# parameters validity is secured +############################################################################## + + def get_grating(self): + """ + Returns the current grating identification (0 to self.get_number_gratings-1) + + Tested : yes + SI check : na + """ + grating = ct.c_int() + self.check(self.dll.ShamrockGetGrating(self.deviceID, ct.byref(grating))) + return grating.value-1 + + def set_grating(self, grating): + """ + Sets the required grating (0 to self.get_number_gratings-1) + + @param int grating: grating identification number + @return: void + + Tested : yes + SI check : na + """ + if not (0 <= grating <= (self.get_number_gratings()-1)): + self.log.error('grating number is not in the validity range') + return + + if isinstance (grating, int): + self.check(self.dll.ShamrockSetGrating(self.deviceID, grating+1)) + else: + self.log.error('set_grating function "grating" parameter needs to be int type') + + def get_number_gratings(self): + """ + Returns the number of gratings in the spectrometer + + @return int number_of_gratings + + Tested : yes + SI check : na + + """ + number_of_gratings = ct.c_int() + self.check(self.dll.ShamrockGetNumberGratings(self.deviceID, ct.byref(number_of_gratings))) + return number_of_gratings.value + + def get_grating_info(self, grating): + """ + Returns grating informations + + @param int grating: grating id + @return dictionnary { 'ruling': float (line/m), + 'blaze wavelength': string (nm) + 'home': int (steps) + 'offset': int (steps)} + + + Tested : yes + SI check : yes + + """ + line = ct.c_float() + blaze = ct.create_string_buffer(32) + home = ct.c_int() + offset = ct.c_int() + dico = {} + + if not (0 <= grating <= (self.get_number_gratings()-1)): + self.log.error('grating number is not in the validity range') + return + + if isinstance (grating, int): + self.check(self.dll.ShamrockGetGratingInfo(self.deviceID, grating+1, + ct.byref(line), + ct.byref(blaze), + ct.byref(home), + ct.byref(offset))) + dico['ruling'] = line.value*1E3 + dico['blaze wavelength (nm)'] = blaze.value + dico['home'] = home.value + dico['offset'] = offset.value + return dico + else: + self.log.error('set_grating_info function "grating" parameter needs to be int type') + + def get_grating_offset(self, grating): + """ + Returns the grating offset (unit is motor steps) + + @param int grating (between 0 and number_of_gratings) + @return int grating offset (step) + + Tested : yes + SI check : na + + """ + if not (0 <= grating <= (self.get_number_gratings()-1)): + self.log.error('grating number is not in the validity range') + return + + if isinstance (grating, int): + grating_offset = ct.c_int() + self.check(self.dll.ShamrockGetGratingOffset(self.deviceID, grating+1, ct.byref(grating_offset))) + return grating_offset.value + else: + self.log.error('get_grating_offset function "grating" parameter needs to be int type') + + def set_grating_offset(self, grating, offset): + """ + Sets the grating offset (unit is motor step) + @param int grating : grating id (0..self.get_number_gratings() + int offset : grating offset (step) + + Tested : yes + SI check : na + + """ + if not (0 <= grating <= (self.get_number_gratings()-1)): + self.log.error('grating number is not in the validity range') + return + + if isinstance (grating, int): + if isinstance(offset, int): + self.check(self.dll.ShamrockSetGratingOffset(self.deviceID, grating+1, offset)) + else: + self.log.error('set_grating_offset function "offset" parameter needs to be int type') + else: + self.log.error('set_grating_offset function "grating" parameter needs to be int type') + +############################################################################## +# Wavelength functions +############################################################################## +# All functions in this section have been tested (03/04/2020) +# Comments have to be homogenized +# SI check is OK +# parameters validity is secured +############################################################################## + + def get_wavelength(self): + """ + Returns the central current wavelength (m) + @return float wavelength (m) + + Tested : yes + SI check : yes + """ + wavelength = ct.c_float() + self.check(self.dll.ShamrockGetWavelength(self.deviceID, ct.byref(wavelength))) + return wavelength.value*1E-9 + + def set_wavelength(self, wavelength): + """ + Sets the new central wavelength + @params float wavelength (m) + + Tested : yes + SI check : yes + + """ + + minwl, maxwl = self.get_wavelength_limit(self.get_grating()) + + if not (minwl <= wavelength <= maxwl): + self.log.error('the wavelength you ask ({0} nm) is not in the range ' + 'of the current grating ( [{1} ; {2}] nm)'.format(wavelength*1E9, minwl*1E9, maxwl*1E9)) + return + + self.dll.ShamrockSetWavelength.argtypes = [ct.c_int32, ct.c_float] + self.check(self.dll.ShamrockSetWavelength(self.deviceID, wavelength*1e9)) + #self._wl = self.get_calibration(self.get_number_of_pixels()) + + return + + def get_wavelength_limit(self, grating): + """ + Returns the wavelength limits (m) of the grating (0-self.get_number_gratings) + @params int grating + + Tested : yes + SI check : yes + + """ + wavelength_min = ct.c_float() + wavelength_max = ct.c_float() + + if not (0 <= grating <= (self.get_number_gratings()-1)): + self.log.error('grating number is not in the validity range') + return + + if isinstance (grating, int): + self.check(self.dll.ShamrockGetWavelengthLimits(self.deviceID, grating+1, ct.byref(wavelength_min) + , ct.byref(wavelength_max))) + return wavelength_min.value*1E-9, wavelength_max.value*1E-9 + else : + self.log.error('get_wavelength_limit function "grating" parameter needs to be int type') + + def get_calibration(self, number_pixels): + """ + Returns the wavelength calibration of each pixel (m) + @params int number_pixels + + Tested : yes + SI check : yes + + Important Note : ShamrockSetNumberPixels and ShamrockSetPixelWidth must have been called + otherwise this function will return -1 + """ + + if (number_pixels == self.get_number_of_pixels()): + wl_array = np.ones((number_pixels,), dtype=np.float32) + self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] + self.check(self.dll.ShamrockGetCalibration(self.deviceID, wl_array.ctypes.data, number_pixels)) + return wl_array*1E-9 + else: + self.log.debug("get_calibration : your argument seems not consistant with the current number of pixels\ + did you really perform shamrock.set_number_of_pixel before to proceed ?") + return -1 + + def set_number_of_pixels(self, number_of_pixels): + """ + Sets the number of pixels of the detector (to prepare for calibration) + :param number_of_pixels: int + :return: nothing + + Tested : yes + SI check : na + """ + if isinstance (number_of_pixels, int): + self.check(self.dll.ShamrockSetNumberPixels(self.deviceID, number_of_pixels)) + else: + self.log.error('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') + return + + def set_pixel_width(self, width): + """ + Sets the pixel width along the dispersion axis (to prepare for calibration) + :param width: float unit is m + :return: nothing + + Tested : yes + SI check : yes + """ + if not (1e-6 <= width <= 100E-6): + self.log.error('the pixel width you ask ({0} um) is not in a ' + 'reasonable range ( [{1} ; {2}] um)'.format(width*1E6, 1, 100)) + return + + self.dll.ShamrockSetPixelWidth.argtypes = [ct.c_int32, ct.c_float] + self.check(self.dll.ShamrockSetPixelWidth(self.deviceID, width*1E6)) + return + + def get_number_of_pixels(self): + """ + Returns the number of pixel that has to be previously set with self.set_number_of_pixels() + :return: int pixel number + + Tested : yes + SI check : na + """ + pixel_number = ct.c_int() + self.check(self.dll.ShamrockGetNumberPixels(self.deviceID, ct.byref(pixel_number))) + return pixel_number.value + + def get_pixel_width(self): + """ + Returns the pixel width along dispersion axis. + Note that pixel width has to be previously set with self.set_pixel_width(width) + :return: int pixel number + + Tested : yes + SI check : yes + + """ + + pixel_width = ct.c_float() + self.check(self.dll.ShamrockGetPixelWidth(self.deviceID, ct.byref(pixel_width))) + return pixel_width.value*1E-6 + +############################################################################## +# Detector functions +############################################################################## +# All functions in this section have been tested (03/04/2020) +# Comments have to be homogenized +# SI check is OK +# parameters validity is secured +############################################################################## + + def get_detector_offset(self): + """ + Returns the detector offset in pixels + :return: int offset + + Tested : yes + SI check : yes + + """ + offset = ct.c_int() + self.check(self.dll.ShamrockGetDetectorOffset(self.deviceID, ct.byref(offset))) + return offset.value + + def set_detector_offset(self, offset): + """ + Sets the detecotor offset in pixels + :param offset : int + :return: nothing + + Tested : yes + SI check : yes + + """ + if isinstance (offset, int): + self.check(self.dll.ShamrockSetDetectorOffset(self.deviceID, offset)) + else : + self.log.error('set_detector_offset function "offset" parameter needs to be int type') + +############################################################################## +# Ports and Slits functions +############################################################################## +#Important note : slits are adressed with an index. +#Index definition can be found here : +#https://pylablib.readthedocs.io/en/latest/_modules/pylablib/aux_libs/devices/AndorShamrock.html +# 1=input_side - 2=input direct - 3=output side - 4=output direct +# IT HAS TO BE TESTED ON SITE because of the lack of Andor official documentation +############################################################################## +# All functions in this section have been tested (03/04/2020) +# Comments have to be homogenized +# SI check is OK +# parameters validity is secured +############################################################################## + + def flipper_mirror_is_present(self, flipper): + """ + Returns 1 if flipper mirror is present, 0 if not + + :param flipper: int 1 is for input, 2 is for output + :return: 1 or 0 + + Test + SI check + + """ + + present = ct.c_int() + if flipper in [1, 2]: + self.check(self.dll.ShamrockFlipperMirrorIsPresent(self.deviceID, flipper, ct.byref(present))) + else: + self.log.error('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') + return present.value + + def get_input_port(self): + """ + Returns the current port for the input flipper mirror. + 0 is for front port, 1 is for side port + in case of no flipper mirror, front port (0) is used + + Tested : yes + SI check : na + + """ + input_port = ct.c_int() + if self.flipper_mirror_is_present(1) == 1: + self.check(self.dll.ShamrockGetFlipperMirror(self.deviceID, 1, ct.byref(input_port))) + return input_port.value + else: + input_port.value=0 + self.log.error('there is no flipper mirror on input port') + return input_port.value + + def get_output_port(self): + """ + Returns the current port for the output flipper mirror. + 0 is for front port, 1 is for side port + in case of no flipper mirror, front port (0) is used + + Tested : yes + SI check : na + + """ + output_port = ct.c_int() + if self.flipper_mirror_is_present(2)==1: + self.check(self.dll.ShamrockGetFlipperMirror(self.deviceID, 2, ct.byref(output_port))) + return output_port.value + else: + output_port.value=0 + self.log.error('there is no flipper mirror on output port') + return output_port.value + + def set_input_port(self, input_port): + """ + Sets the input port - 0 is for front port, 1 is for side port + + :param input_port: int. has to be in [0, 1] + :return: nothing + + Tested : yes + SI check : yes + + """ + if self.flipper_mirror_is_present(1)==1: + if input_port in [0,1] : + self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 1, input_port)) + else: + self.log.error('set_input_port function : input port should be 0 (front) or 1 (side)') + else: + self.log.error('there is no flipper mirror on input port') + + def set_output_port(self, output_port): + """ + Sets the input port - 0 is for front port, 1 is for side port + + :param input_port: int. has to be in [0, 1] + :return: nothing + + Tested : yes + SI check : yes + + """ + if self.flipper_mirror_is_present(2) == 1: + if output_port in [0, 1]: + self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 2, output_port)) + else: + self.log.error('set_output_port function : output port should be 0 (front) or 1 (side)') + else: + self.log.error('there is no flipper mirror on output port') + + def slit_index(self, flipper, port): + """ + Returns the slit index whether there is a slit or not + :param flipper: string flipper - within ['input', 'output'] + :param port: int - within[0,1] for front or side port + :return: int slit_index as defined by Andor convention + + Note : just a local function for ease + """ + if flipper=='input': + if port==1: slit_index=1 + elif port==0: slit_index=2 + else: slit_index=0 + elif flipper=='output': + if port==1:slit_index=3 + elif port==0:slit_index=4 + else: slit_index=0 + else: slit_index=0 + + return slit_index + + def get_auto_slit_width(self, flipper, port): + """ + Returns the input slit width (um) in case of a motorized slit, + :param string flipper - within ['input', 'output'] + int port - within[0,1] for front or side port + :return int offset - slit width, unit is meter (SI) + + Tested : yes + SI check : yes + + """ + + slit_index=self.slit_index(flipper, port) + if slit_index==0: + self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + return + + slit_width = ct.c_float() + + if self.auto_slit_is_present(flipper, port) == 1: + self.check(self.dll.ShamrockGetAutoSlitWidth(self.deviceID, slit_index, ct.byref(slit_width))) + return slit_width.value*1E-6 + else: + self.log.error('there is no slit on this port !') + + def set_auto_slit_width(self, flipper, port, slit_width): + """ + Sets the new slit width for the required slit + :param flipper: string flipper - within ['input', 'output'] + :param port: int - within[0,1] for front or side port + :param slit_width: float - unit is meter (SI) + :return: nothing + + Tested : yes + SI check : yes + """ + slit_index = self.slit_index(flipper, port) + if slit_index == 0: + self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + return + self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] + if self.auto_slit_is_present(flipper, port): + if (self.SLIT_MIN_WIDTH <= slit_width <=self.SLIT_MAX_WIDTH): + self.check(self.dll.ShamrockSetAutoSlitWidth(self.deviceID, slit_index, slit_width*1E6)) + else: + self.log.error('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) + else: + self.log.error('there is no slit on this port') + return + + def auto_slit_is_present(self, flipper, port): + """ + Return whether the required slit is present or not + :param flipper: string flipper - within ['input', 'output'] + :param port: int - within[0,1] for front or side port + :return: 1 if present, 0 if not + + Tested : yes + SI check : yes + """ + slit_index = self.slit_index(flipper, port) + if slit_index == 0: + self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + return + present = ct.c_int() + self.check(self.dll.ShamrockAutoSlitIsPresent(self.deviceID, slit_index, ct.byref(present))) + return present.value + + +############################################################################## +# Shamrock wrapper +############################################################################## +# sdk basic functions + + def shamrock_initialize(self): + self.check(self.dll.ShamrockInitialize()) + return + + def shamrock_close(self): + self.check(self.dll.ShamrockClose()) + return + + def goto_zero_order(self): + """ + Strange function. No documentation from Andor. Seems to be equivalent to self.set_wavelength(0) ? + """ + self.check(self.dll.ShamrockGotoZeroOrder(self.deviceID)) + return + + def at_zero_order(self): + pass + + def shamrock_grating_is_present(self): + """ + Finds if grating is present + + @returns int 1 if present 0 if not + + Tested : yes + """ + present = ct.c_int() + self.check(self.dll.ShamrockGratingIsPresent(self.deviceID, ct.byref(present))) + return present.value + + + + + diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py new file mode 100644 index 0000000000..a0793c4e73 --- /dev/null +++ b/interface/spectrometer_complete_interface.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +""" +Interface module for spectrometer hardware + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +from core.interface import abstract_interface_method +from core.meta import InterfaceMetaclass + + +class SpectrometerInterface(metaclass=InterfaceMetaclass): + """ + This is the Interface class to define the controls for spectrometer hardware + """ + + ############################################################################## + # Gratings functions + ############################################################################## + + @abstract_interface_method + def get_grating(self): + """ + Returns the current grating identification (0 to self.get_number_gratings-1) + """ + pass + + @abstract_interface_method + def set_grating(self, grating): + """ + Sets the required grating (0 to self.get_number_gratings-1) + + @param int grating: grating identification number + @return: void + """ + pass + + @abstract_interface_method + def get_number_gratings(self): + """ + Returns the number of gratings in the spectrometer + + @return int number_of_gratings + """ + pass + + @abstract_interface_method + def get_grating_offset(self, grating): + """ + Returns the grating offset (unit is motor steps) + + @param int grating (between 0 and number_of_gratings) + @return int grating offset (step) + """ + pass + + @abstract_interface_method + def set_grating_offset(self, grating, offset): + """ + Sets the grating offset (unit is motor step) + + @param int grating : grating id (0..self.get_number_gratings() + int offset : grating offset (step) + """ + pass + + ############################################################################## + # Wavelength functions + ############################################################################## + + @abstract_interface_method + def get_wavelength(self): + """ + Returns the central current wavelength (m) + @return float wavelength (m) + """ + pass + + @abstract_interface_method + def set_wavelength(self, wavelength): + """ + Sets the new central wavelength + @params float wavelength (m) + """ + + pass + + @abstract_interface_method + def get_wavelength_limit(self, grating): + """ + Returns the wavelength limits (m) of the grating (0-self.get_number_gratings) + @params int grating + """ + pass + + @abstract_interface_method + def get_calibration(self, number_pixels): + """ + Returns the wavelength calibration of each pixel (m) + @params int number_pixels + """ + pass + + @abstract_interface_method + def get_number_of_pixels(self): + """ + Returns the number of pixel that has to be previously set with self.set_number_of_pixels() + :return: int pixel number + """ + pass + + @abstract_interface_method + def set_number_of_pixels(self, number_of_pixels): + """ + Sets the number of pixels of the detector (to prepare for calibration) + :param number_of_pixels: int + :return: nothing + """ + pass + + @abstract_interface_method + def get_pixel_width(self): + """ + Returns the pixel width along dispersion axis. + Note that pixel width has to be previously set with self.set_pixel_width(width) + :return: int pixel number + """ + pass + + @abstract_interface_method + def set_pixel_width(self, width): + """ + Sets the pixel width along the dispersion axis (to prepare for calibration) + :param width: float unit is m + :return: nothing + """ + pass + + ############################################################################## + # Detector functions + ############################################################################## + + @abstract_interface_method + def get_detector_offset(self): + """ + Returns the detector offset in pixels + :return: int offset + """ + pass + + @abstract_interface_method + def set_detector_offset(self, offset): + """ + Sets the detecotor offset in pixels + :param offset : int + :return: nothing + """ + pass + + ############################################################################## + # Ports and Slits functions + ############################################################################## + + @abstract_interface_method + def flipper_mirror_is_present(self, flipper): + """ + Returns 1 if flipper mirror is present, 0 if not + + :param flipper: int 1 is for input, 2 is for output + :return: 1 or 0 + """ + + pass + + @abstract_interface_method + def get_input_port(self): + """ + Returns the current port for the input flipper mirror. + 0 is for front port, 1 is for side port + in case of no flipper mirror, front port (0) is used + """ + pass + + @abstract_interface_method + def set_input_port(self, input_port): + """ + Sets the input port - 0 is for front port, 1 is for side port + + :param input_port: int. has to be in [0, 1] + :return: nothing + """ + pass + + @abstract_interface_method + def get_output_port(self): + """ + Returns the current port for the output flipper mirror. + 0 is for front port, 1 is for side port + in case of no flipper mirror, front port (0) is used + """ + pass + + @abstract_interface_method + def set_output_port(self, output_port): + """ + Sets the input port - 0 is for front port, 1 is for side port + + :param input_port: int. has to be in [0, 1] + :return: nothing + """ + pass + + @abstract_interface_method + def get_auto_slit_width(self, flipper, port): + """ + Returns the input slit width (um) in case of a motorized slit, + :param string flipper - within ['input', 'output'] + int port - within[0,1] for front or side port + :return int offset - slit width, unit is meter (SI) + """ + pass + + @abstract_interface_method + def set_auto_slit_width(self, flipper, port, slit_width): + """ + Sets the new slit width for the required slit + :param flipper: string flipper - within ['input', 'output'] + :param port: int - within[0,1] for front or side port + :param slit_width: float - unit is meter (SI) + :return: nothing + """ + pass + + @abstract_interface_method + def auto_slit_is_present(self, flipper, port): + """ + Return whether the required slit is present or not + :param flipper: string flipper - within ['input', 'output'] + :param port: int - within[0,1] for front or side port + :return: 1 if present, 0 if not + """ + pass + diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py new file mode 100644 index 0000000000..24c9cd770a --- /dev/null +++ b/logic/spectrum_logic.py @@ -0,0 +1,574 @@ +# -*- coding: utf-8 -*- +""" +This file contains the Qudi logic class that captures and processes photoluminescence +spectra and the spot image. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +from qtpy import QtCore +from collections import OrderedDict +import numpy as np +import matplotlib.pyplot as plt + +from core.connector import Connector +from core.statusvariable import StatusVar +from core.util.mutex import Mutex +from core.util.network import netobtain +from logic.generic_logic import GenericLogic +from core.configoption import ConfigOption + +from datetime import date + + +class SpectrumLogic(GenericLogic): + """This logic module gathers data from the spectrometer. + """ + + # declare connectors + spectrometer = Connector(interface='SpectrometerInterface') + camera = Connector(interface='CameraInterface') + savelogic = Connector(interface='SaveLogic', optional=True) + + # declare status variables + _spectrum_data = StatusVar('spectrum_data', np.empty((2, 0))) + _background_data = StatusVar('background_data', np.empty((2, 0))) + _image_data = StatusVar('image_data', np.empty((2, 0))) + + _grating = StatusVar('grating', 1) + _grating_offset = StatusVar('grating_offset', 0) + + _center_wavelength = StatusVar('center_wavelength', 400) + _wavelength_range = StatusVar('wavelength_range', np.empty((2, 0))) + _number_of_pixels = StatusVar('number_of_pixels', 512) + _pixel_width = StatusVar('pixel_width', 1e-4) + + _detector_offset = StatusVar('detector_offset', 0) + + _input_port = StatusVar('input_port', 1) + _output_port = StatusVar('output_port', 0) + _input_slit_width = StatusVar('input_slit_width', 100) + _output_slit_width = StatusVar('output_slit_width', 100) + + # Allow to set a default save directory in the config file : + _default_save_file_path = ConfigOption('default_save_file_path') + _save_file_path = StatusVar('save_file_path', _default_save_file_path) + + ############################################################################## + # Basic functions + ############################################################################## + + def __init__(self, **kwargs): + """ Create SpectrumLogic object with connectors and status variables loaded. + + @param dict kwargs: optional parameters + """ + super().__init__(**kwargs) + + # locking for thread safety + self.threadlock = Mutex() + + def on_activate(self): + """ Initialisation performed during activation of the module. + """ + self.spectrometer_device = self.spectrometer() + self.camera_device = self.camera() + + self._save_logic = self.savelogic() + + def on_deactivate(self): + """ Deinitialisation performed during deactivation of the module. + """ + if self.module_state() != 'idle' and self.module_state() != 'deactivated': + pass + + ############################################################################## + # Save functions + ############################################################################## + + def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): + """ + Method used to save the data using the savelogic module + + :param data_type: + :param file_name: + :param save_path: + :return: nothing + """ + data = OrderedDict() + today = date.today() + if data_type == 'image': + data = self._image_data[:, :] + else: + if data_type == 'spectrum': + data['Wavelength (m)'] = self._spectrum_data[0, :] + data['PL intensity (u.a)'] = self._spectrum_data[1, :] + elif data_type == 'background': + data['Wavelength (m)'] = self._background_data[0, :] + data['PL intensity (u.a)'] = self._background_data[1, :] + else: + self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' + ' \'background\' or \'image\'') + if file_name is not None: + file_label = file_name + else: + file_label = '{}_'.format(data_type) + today.strftime("%d%m%Y") + if save_path is not None: + self._save_file_path = save_path + self._save_logic.save_data(data, + filepath=self._save_file_path, + filelabel=file_label) + self.log.info('{} data saved in {}'.format(data_type.capitalize(), self._save_file_path)) + + ############################################################################## + # Acquisition functions + ############################################################################## + + def start_acquisition(self): + """ + Method proceeding to data acquisition. + + :return: 1 if succeed or 0 if failed + """ + + if self.camera_device.start_single_acquisition(): + data = self.camera_device.get_acquired_data() + self.log.info('Single spectrum acquisition achieved with success ') + return data + else: + self.log.info('Single spectrum acquisition aborted ') + return 0 + + def stop_acquisition(self): + """ + Method aborting data acquisition and all the related procedures + :return: 1 if succeed or 0 if failed + """ + + if self.camera_device.stop_acquisition(): + self.log.info('Acquisition aborted with success ') + return 1 + else: + self.log.error('Acquisition can\'t be aborted : try again or disconnect the device ') + return 0 + + def acquire_spectrum(self): + pass + + def acquire_background(self): + pass + + def acquire_image(self): + pass + + @property + def spectrum_data(self): + return self._spectrum_data + + @property + def background_data(self): + return self._background_data + + @property + def image_data(self): + return self._image_data + + ############################################################################## + # Spectrometer functions + ############################################################################## + # All functions defined in this part should be used to + # + # + ############################################################################## + # Gratings functions + ############################################################################## + + @property + def grating(self): + grating_number = self.spectrometer_device.get_grating() + is_int = isinstance(grating_number, int) + if is_int: + self._grating = grating_number + return grating_number + else: + self.log.error('Your hardware getter function \'get_grating()\' is not returning an integer ') + return 0 + + + @grating.setter + def grating(self, grating_number): + number_gratings = self.spectrometer_device.get_number_gratings() + is_int = isinstance(grating_number, int) + is_in_range = 0 < grating_number < number_gratings + is_change = grating_number != self._grating + if is_int and is_in_range and is_change: + self.spectrometer_device.set_grating(grating_number) + self.log.info('Spectrometer grating has been changed correctly ') + self._grating = grating_number + else: + if is_int: + self.log.debug('Grating parameter is not correct : it must be an integer ') + if is_in_range: + self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' + .format(number_gratings-1)) + else: + self.log.info('Grating parameter has not been changed') + + @property + def grating_offset(self, grating_number): + number_gratings = self.spectrometer_device.get_number_gratings() + var_is_int = isinstance(grating_number, int) + var_is_in_range = 0 < grating_number < number_gratings + var_is_change = grating_number != self._grating + if var_is_int and var_is_in_range and var_is_change: + grating_offset = self.spectrometer_device.get_grating_offset() + is_int = isinstance(grating_number, int) + if is_int: + self._grating_offset = grating_offset + return grating_offset + else: + self.log.error('Your hardware getter function \'get_grating_offset()\' is not returning an integer ') + return 0 + else: + if var_is_int: + self.log.debug('Grating parameter is not correct : it must be an integer ') + if var_is_in_range: + self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' + .format(number_gratings-1)) + else: + self.log.info('Grating parameter has not been changed') + return 0 + + @grating_offset.setter + def grating_offset(self, grating_number, grating_offset): + number_gratings = self.spectrometer_device.get_number_gratings() + grating_is_int = isinstance(grating_number, int) + grating_is_in_range = -1 < grating_number < number_gratings + grating_is_change = grating_number != self._grating + grating_is_correct = grating_is_int and grating_is_in_range and grating_is_change + number_pixels = self.number_of_pixels() + offset_min = -number_pixels//2 - number_pixels % 2 + offset_max = number_pixels//2 + offset_is_int = isinstance(grating_offset, int) + offset_is_in_range = offset_min < grating_offset < offset_max + offset_is_change = grating_offset != self._grating_offset + offset_is_correct = offset_is_int and offset_is_in_range and offset_is_change + if grating_is_correct and offset_is_correct: + self.spectrometer_device.set_grating_offset(grating_offset) + self.log.info('Spectrometer grating offset has been changed correctly ') + self._grating = grating_number + else: + if grating_is_int: + self.log.debug('Grating parameter is not correct : it must be an integer ') + elif grating_is_in_range: + self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' + .format(number_gratings-1)) + elif grating_is_change: + self.log.info('Grating parameter has not been changed') + elif offset_is_int: + self.log.debug('Offset parameter is not correct : it must be an integer ') + elif offset_is_in_range: + self.log.debug('Offset parameter is not correct : it must be in range {} to {} ' + .format(offset_min, offset_max)) + elif offset_is_change: + self.log.info('Offset parameter has not been changed') + + ############################################################################## + # Wavelength functions + ############################################################################## + + @property + def center_wavelength(self): + wavelength = self.spectrometer_device.get_wavelength() + is_float = isinstance(wavelength, float) + if is_float: + self._center_wavelength = wavelength + return wavelength + else: + self.log.error('Your hardware getter function \'get_wavelength()\' is not returning a float ') + return 0 + + @center_wavelength.setter + def center_wavelength(self, wavelength): + wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) + is_float = isinstance(wavelength, float) + is_in_range = wavelength_min < wavelength < wavelength_max + is_change = wavelength != self._center_wavelength + if is_float and is_in_range and is_change: + self.spectrometer_device.set_wavelength(wavelength) + self.log.info('Spectrometer wavelength has been changed correctly ') + self._center_wavelength = wavelength + else: + if is_float: + self.log.debug('Wavelength parameter is not correct : it must be a float ') + elif is_in_range: + self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' + .format(wavelength_min, wavelength_max)) + else: + self.log.info('Wavelength parameter has not been changed') + + @property + def wavelength_range(self): + wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) + wavelength_range = self.spectrometer_device.get_calibration(self._number_of_pixels) + is_ndarray = isinstance(wavelength_range, np.ndarray) + is_in_range = np.min(wavelength_range) > wavelength_min and np.max(wavelength_range) > wavelength_max + if is_ndarray and is_in_range: + self._wavelength_range = wavelength_range + return wavelength_range + else: + if is_ndarray: + self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ndarray ') + else: + self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ' + 'wavelength in range of the current grating wavelength limits ') + return 0 + + @property + def number_of_pixels(self): + number_pixels = self.spectrometer_device.get_number_of_pixels() + is_int = isinstance(number_pixels, int) + is_positive = 0 < number_pixels + if is_int and is_positive: + self._number_of_pixels = number_pixels + return number_pixels + else: + if is_int: + self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a int ') + else: + self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a ' + 'positive number ') + return 0 + + @number_of_pixels.setter + def number_of_pixels(self, number_pixels): + is_int = isinstance(number_pixels, int) + is_positive = 0 < number_pixels + is_change = number_pixels != self._number_of_pixels + if is_int and is_positive and is_change: + self.spectrometer_device.get_pixel_width(number_pixels) + self.log.info('Number of pixels has been changed correctly ') + self._number_of_pixels = number_pixels + else: + if is_int: + self.log.debug('Number of pixels parameter is not correct : it must be a int ') + elif is_positive: + self.log.debug('Number of pixels parameter is not correct : it must be positive ') + else: + self.log.info('Number of pixels parameter has not been changed') + + @property + def pixel_width(self): + pixel_width = self.spectrometer_device.get_pixel_width() + is_float = isinstance(pixel_width, float) + is_positive = 0 < pixel_width + if is_float and is_positive: + self._pixel_width = pixel_width + return pixel_width + else: + if is_float: + self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a float ') + else: + self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a ' + 'positive number ') + return 0 + + @pixel_width.setter + def pixel_width(self, pixel_width): + is_float = isinstance(pixel_width, float) + is_positive = 0 < pixel_width + is_change = pixel_width != self._pixel_width + if is_float and is_positive and is_change: + self.spectrometer_device.set_pixel_width(pixel_width) + self.log.info('Pixel width has been changed correctly ') + self._pixel_width = pixel_width + else: + if is_float: + self.log.debug('Pixel width parameter is not correct : it must be a float ') + elif is_positive: + self.log.debug('Pixel width parameter is not correct : it must be positive ') + else: + self.log.info('Pixel width parameter has not been changed') + + ############################################################################## + # Detector functions + ############################################################################## + + @property + def detector_offset(self): + offset = self.spectrometer_device.get_detector_offset() + is_int = isinstance(offset, int) + if is_int: + self._detector_offset = offset + return offset + else: + self.log.error('Your hardware getter function \'get_detector_offset()\' is not returning a int ') + return 0 + + @detector_offset.setter + def detector_offset(self, detector_offset): + number_pixels = 514 #TODO : add the Newton funtion returning the number of pixels (Hardcoding) + offset_min = -number_pixels//2 - 1 + offset_max = number_pixels//2 + is_int = isinstance(detector_offset, int) + is_in_range = offset_min - 1 < detector_offset < offset_max + 1 + is_change = detector_offset != self._detector_offset + if is_int and is_in_range and is_change: + self.spectrometer_device.set_detector_offset(detector_offset) + self.log.info('Detector offset has been changed correctly ') + self._detector_offset = detector_offset + else: + if is_int: + self.log.debug('Detector offset parameter is not correct : it must be a int ') + elif is_in_range: + self.log.debug('Detector offset parameter is not correct : it must be in range {} to {} ' + .format(offset_min, offset_max)) + else: + self.log.info('Detector offset parameter has not been changed') + + ############################################################################## + # Ports and Slits functions + ############################################################################## + + @property + def input_port(self): + input_port = self.spectrometer_device.get_input_port() + is_int = isinstance(input_port, int) + is_in_range = -1 < input_port < 2 + if is_int and is_in_range: + self._input_port = input_port + return input_port + else: + if is_int: + self.log.error('Your hardware getter function \'get_input_port()\' is not returning a int ') + else: + self.log.error('Your hardware getter function \'get_input_port()\' is not in range 0 to 1 ') + return 0 + + @input_port.setter + def input_port(self, input_port): + side_port_possible = self.spectrometer_device.flipper_mirror_is_present(1) + is_int = isinstance(input_port, int) + is_in_range = -1 < input_port < 2 + is_change = input_port != self._input_port + if is_int and is_in_range and is_change: + if side_port_possible or input_port == 0: + self.spectrometer_device.set_input_port(input_port) + self.log.info('Input port has been changed correctly ') + self._input_port = input_port + else: + self.log.debug('Your hardware do not have any flipper mirror present at the input port ') + else: + if is_int: + self.log.debug('Input port parameter is not correct : it must be a int ') + elif is_in_range: + self.log.debug('Input port parameter is not correct : it must be 0 or 1 ') + else: + self.log.info('Input port parameter has not been changed') + + @property + def output_port(self): + output_port = self.spectrometer_device.get_output_port() + is_int = isinstance(output_port, int) + is_in_range = -1 < output_port < 2 + if is_int and is_in_range: + self._input_port = output_port + return output_port + else: + if is_int: + self.log.error('Your hardware getter function \'get_output_port()\' is not returning a int ') + else: + self.log.error('Your hardware getter function \'get_output_port()\' is not in range 0 to 1 ') + return 0 + + @output_port.setter + def output_port(self, output_port): + side_port_possible = self.spectrometer_device.flipper_mirror_is_present(2) + is_int = isinstance(output_port, int) + is_in_range = -1 < output_port < 2 + is_change = output_port != self._output_port + if is_int and is_in_range and is_change: + if side_port_possible or output_port == 0: + self.spectrometer_device.set_output_port(output_port) + self.log.info('Output port has been changed correctly ') + self._output_port = output_port + else: + self.log.debug('Your hardware do not have any flipper mirror present at the output port ') + else: + if is_int: + self.log.debug('Output port parameter is not correct : it must be a int ') + elif is_in_range: + self.log.debug('Output port parameter is not correct : it must be 0 or 1 ') + else: + self.log.info('Output port parameter has not been changed') + + @property + def input_slit_width(self): + slit_width = self.spectrometer_device.get_auto_slit_width('input', self._input_port) + is_float = isinstance(slit_width, float) + if is_float: + self._input_slit_width = slit_width + return slit_width + else: + self.log.error('Your hardware getter function \'get_auto_slit_width()\' is not returning a float ') + return 0 + + @input_slit_width.setter + def input_slit_width(self, slit_width): + slit_is_present = self.spectrometer_device.auto_slit_is_present('input', self._input_port) + is_float = isinstance(slit_width, float) + if is_float: + if slit_is_present: + self.spectrometer_device.set_auto_slit_width('input', self._input_port, slit_width) + self.log.info('Output slit width has been changed correctly ') + self._input_slit_width = slit_width + else: + self.log.debug('Your hardware do not have any auto slit present at the selected input port ') + else: + self.log.debug('Input slit width parameter is not correct : it must be a float ') + + + @property + def output_slit_width(self): + slit_width = self.spectrometer_device.get_auto_slit_width('output', self._output_port) + is_float = isinstance(slit_width, float) + if is_float: + self._output_slit_width = slit_width + return slit_width + else: + self.log.error('Your hardware getter function \'get_auto_slit_width()\' is not returning a float ') + + @output_slit_width.setter + def output_slit_width(self, slit_width): + slit_is_present = self.spectrometer_device.auto_slit_is_present('output', self._output_port) + is_float = isinstance(slit_width, float) + if is_float: + if slit_is_present: + self.spectrometer_device.set_auto_slit_width('output', self._output_port, slit_width) + self.log.info('Output slit width has been changed correctly ') + self._output_slit_width = slit_width + else: + self.log.debug('Your hardware do not have any auto slit present at the selected output port ') + else: + self.log.debug('Output slit width parameter is not correct : it must be a float ') + + ############################################################################## + # Camera functions + ############################################################################## + + def set_read_mode(self, mode): + self.camera_device.set_read_mode(mode) From cd7e690624f5fb6ad07a3aaa5023ed04f1a57d84 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Tue, 7 Apr 2020 19:19:03 +0200 Subject: [PATCH 02/49] hardware spectro Newton 940 --- hardware/camera/andor/Newton_940.py | 754 ++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 hardware/camera/andor/Newton_940.py diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py new file mode 100644 index 0000000000..6879243aab --- /dev/null +++ b/hardware/camera/andor/Newton_940.py @@ -0,0 +1,754 @@ +# -*- coding: utf-8 -*- + +""" +This hardware module implement the camera spectrometer interface to use an Andor Camera. +It use a dll to interface with instruments via USB (only available physical interface) +This module does aim at replacing Solis. + +--- + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +from enum import Enum +from ctypes import * +import numpy as np + +from core.module import Base, ConfigOption + + +from interface.camera_interface import CameraInterface + + +class ReadMode(Enum): + FVB = 0 + MULTI_TRACK = 1 + RANDOM_TRACK = 2 + SINGLE_TRACK = 3 + IMAGE = 4 + +class AcquisitionMode(Enum): + SINGLE_SCAN = 1 + ACCUMULATE = 2 + KINETICS = 3 + FAST_KINETICS = 4 + RUN_TILL_ABORT = 5 + +class TriggerMode(Enum): + INTERNAL = 0 + EXTERNAL = 1 + EXTERNAL_START = 6 + EXTERNAL_EXPOSURE = 7 + SOFTWARE_TRIGGER = 10 + EXTERNAL_CHARGE_SHIFTING = 12 + +ERROR_DICT = { + 20001: "DRV_ERROR_CODES", + 20002: "DRV_SUCCESS", + 20003: "DRV_VXNOTINSTALLED", + 20006: "DRV_ERROR_FILELOAD", + 20007: "DRV_ERROR_VXD_INIT", + 20010: "DRV_ERROR_PAGELOCK", + 20011: "DRV_ERROR_PAGE_UNLOCK", + 20013: "DRV_ERROR_ACK", + 20024: "DRV_NO_NEW_DATA", + 20026: "DRV_SPOOLERROR", + 20034: "DRV_TEMP_OFF", + 20035: "DRV_TEMP_NOT_STABILIZED", + 20036: "DRV_TEMP_STABILIZED", + 20037: "DRV_TEMP_NOT_REACHED", + 20038: "DRV_TEMP_OUT_RANGE", + 20039: "DRV_TEMP_NOT_SUPPORTED", + 20040: "DRV_TEMP_DRIFT", + 20050: "DRV_COF_NOTLOADED", + 20053: "DRV_FLEXERROR", + 20066: "DRV_P1INVALID", + 20067: "DRV_P2INVALID", + 20068: "DRV_P3INVALID", + 20069: "DRV_P4INVALID", + 20070: "DRV_INIERROR", + 20071: "DRV_COERROR", + 20072: "DRV_ACQUIRING", + 20073: "DRV_IDLE", + 20074: "DRV_TEMPCYCLE", + 20075: "DRV_NOT_INITIALIZED", + 20076: "DRV_P5INVALID", + 20077: "DRV_P6INVALID", + 20083: "P7_INVALID", + 20089: "DRV_USBERROR", + 20091: "DRV_NOT_SUPPORTED", + 20095: "DRV_INVALID_TRIGGER_MODE", + 20099: "DRV_BINNING_ERROR", + 20990: "DRV_NOCAMERA", + 20991: "DRV_NOT_SUPPORTED", + 20992: "DRV_NOT_AVAILABLE" +} + +class Newton940(Base, CameraInterface): + """ + Hardware class for Andors Newton940 + """ + + _modtype = 'camera' + _modclass = 'hardware' + + _default_exposure = ConfigOption('default_exposure', 1.0) + _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') + _default_temperature = ConfigOption('default_temperature', -70) + _default_cooler_on = ConfigOption('default_cooler_on', True) + _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') + _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') + #_dll_location = ConfigOption('dll_location', missing='error') + #_dll_location = 'ATMCD32D.dll' + + _exposure = _default_exposure + _temperature = _default_temperature + _cooler_on = _default_cooler_on + _read_mode = _default_read_mode + _acquisition_mode = _default_acquisition_mode + _gain = 0 + _width = 0 + _height = 0 + _last_acquisition_mode = None # useful if config changes during acq + _supported_read_mode = ReadMode # TODO: read this from camera, all readmodes are available for iXon Ultra + _max_cooling = -100 + _live = False + _camera_name = 'iXon Ultra 897' + _shutter = "closed" + _trigger_mode = _default_trigger_mode + _scans = 1 #TODO get from camera + _acquiring = False + + def on_activate(self): + """ Initialisation performed during activation of the module. + """ + # self.cam.SetAcquisitionMode(1) # single + # self.cam.SetTriggerMode(0) # internal + # self.cam.SetCoolerMode(0) # Returns to ambient temperature on ShutDown + # self.set_cooler_on_state(self._cooler_on) + # self.set_exposure(self._exposure) + # self.set_setpoint_temperature(self._temperature) + #self.dll = cdll.LoadLibrary(self._dll_location) + self.dll = cdll.LoadLibrary('C:/temp/atmcd64d.dll') + + self.dll.Initialize() + nx_px, ny_px = c_int(), c_int() + self._get_detector(nx_px, ny_px) + self._width, self._height = nx_px.value, ny_px.value + self._set_read_mode(self._read_mode) + self._set_trigger_mode(self._trigger_mode) + self._set_exposuretime(self._exposure) + self._set_acquisition_mode(self._acquisition_mode) + + def on_deactivate(self): + """ Deinitialisation performed during deactivation of the module. + """ + self.stop_acquisition() + self._set_shutter(0, 0, 0.1, 0.1) + self._shut_down() + + def get_name(self): + """ Retrieve an identifier of the camera that the GUI can print + + @return string: name for the camera + """ + return self._camera_name + + def get_size(self): + """ Retrieve size of the image in pixel + + @return tuple: Size (width, height) + """ + return self._width, self._height + + def support_live_acquisition(self): + """ Return whether or not the camera can take care of live acquisition + + @return bool: True if supported, False if not + """ + return False + + def start_live_acquisition(self): + """ Start a continuous acquisition + + @return bool: Success ? + """ + if self._support_live: + self._live = True + self._acquiring = False + + return False + + def start_single_acquisition(self): + """ Start a single acquisition + + @return bool: Success ? + """ + if self._shutter == 'closed': + msg = self._set_shutter(0, 1, 0.1, 0.1) + if msg == 'DRV_SUCCESS': + self._shutter = 'open' + else: + self.log.error('shutter did not open.{0}'.format(msg)) + + if self._live: + return -1 + else: + self._acquiring = True # do we need this here? + msg = self._start_acquisition() + if msg != "DRV_SUCCESS": + return False + + self._acquiring = False + return True + + def stop_acquisition(self): + """ Stop/abort live or single acquisition + + @return bool: Success ? + """ + msg = self._abort_acquisition() + if msg == "DRV_SUCCESS": + self._live = False + self._acquiring = False + return True + else: + return False + + def get_acquired_data(self): + """ Return an array of last acquired image. + + @return numpy array: image data in format [[row],[row]...] + + Each pixel might be a float, integer or sub pixels + """ + + width = self._width + height = self._height + + if self._read_mode == 'IMAGE': + if self._acquisition_mode == 'SINGLE_SCAN': + dim = width * height + elif self._acquisition_mode == 'KINETICS': + dim = width * height * self._scans + elif self._acquisition_mode == 'RUN_TILL_ABORT': + dim = width * height + else: + self.log.error('Your acquisition mode is not covered currently') + elif self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'FVB': + if self._acquisition_mode == 'SINGLE_SCAN': + dim = width + elif self._acquisition_mode == 'KINETICS': + dim = width * self._scans + else: + self.log.error('Your acquisition mode is not covered currently') + + dim = int(dim) + image_array = np.zeros(dim) + cimage_array = c_int * dim + cimage = cimage_array() + + # this will be a bit hacky + if self._acquisition_mode == 'RUN_TILL_ABORT': + error_code = self.dll.GetOldestImage(pointer(cimage), dim) + else: + error_code = self.dll.GetAcquiredData(pointer(cimage), dim) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) + else: + self.log.debug('image length {0}'.format(len(cimage))) + for i in range(len(cimage)): + # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode + image_array[i] = cimage[i] + + image_array = np.reshape(image_array, (self._width, self._height)) + + self._cur_image = image_array + return image_array + + def set_exposure(self, exposure): + """ Set the exposure time in seconds + + @param float time: desired new exposure time + + @return bool: Success? + """ + msg = self._set_exposuretime(exposure) + if msg == "DRV_SUCCESS": + self._exposure = exposure + return True + else: + return False + + def get_exposure(self): + """ Get the exposure time in seconds + + @return float exposure time + """ + self._get_acquisition_timings() + return self._exposure + + # not sure if the distinguishing between gain setting and gain value will be problematic for + # this camera model. Just keeping it in mind for now. + #TODO: Not really funcitonal right now. + def set_gain(self, gain): + """ Set the gain + + @param float gain: desired new gain + + @return float: new exposure gain + """ + n_pre_amps = self._get_number_preamp_gains() + msg = '' + if (gain >= 0) & (gain < n_pre_amps): + msg = self._set_preamp_gain(gain) + else: + self.log.warning('Choose gain value between 0 and {0}'.format(n_pre_amps-1)) + if msg == 'DRV_SUCCESS': + self._gain = gain + else: + self.log.warning('The gain wasn\'t set. {0}'.format(msg)) + return self._gain + + def get_gain(self): + """ Get the gain + + @return float: exposure gain + """ + _, self._gain = self._get_preamp_gain() + return self._gain + + def get_ready_state(self): + """ Is the camera ready for an acquisition ? + + @return bool: ready ? + """ + status = c_int() + self._get_status(status) + if ERROR_DICT[status.value] == 'DRV_IDLE': + return True + else: + return False + +# soon to be interface functions for using +# a camera as a part of a (slow) photon counter + def set_up_counter(self): + check_val = 0 + if self._shutter == 'closed': + msg = self._set_shutter(0, 1, 0.1, 0.1) + if msg == 'DRV_SUCCESS': + self._shutter = 'open' + else: + self.log.error('Problems with the shutter.') + check_val = -1 + ret_val1 = self._set_trigger_mode('EXTERNAL') + ret_val2 = self._set_acquisition_mode('RUN_TILL_ABORT') + # let's test the FT mode + # ret_val3 = self._set_frame_transfer(True) + error_code = self.dll.PrepareAcquisition() + error_msg = ERROR_DICT[error_code] + if error_msg == 'DRV_SUCCESS': + self.log.debug('prepared acquisition') + else: + self.log.debug('could not prepare acquisition: {0}'.format(error_msg)) + self._get_acquisition_timings() + if check_val == 0: + check_val = ret_val1 | ret_val2 + + if msg != 'DRV_SUCCESS': + ret_val3 = -1 + else: + ret_val3 = 0 + + check_val = ret_val3 | check_val + + return check_val + + def count_odmr(self, length): + first, last = self._get_number_new_images() + self.log.debug('number new images:{0}'.format((first, last))) + if last - first + 1 < length: + while last - first + 1 < length: + first, last = self._get_number_new_images() + else: + self.log.debug('acquired too many images:{0}'.format(last - first + 1)) + + images = [] + for i in range(first, last + 1): + img = self._get_images(i, i, 1) + images.append(img) + self.log.debug('expected number of images:{0}'.format(length)) + self.log.debug('number of images acquired:{0}'.format(len(images))) + return np.array(images).transpose() + + def get_down_time(self): + return self._exposure + + def get_counter_channels(self): + width, height = self.get_size() + num_px = width * height + return [i for i in map(lambda x: 'px {0}'.format(x), range(num_px))] + +# non interface functions regarding camera interface + def _abort_acquisition(self): + error_code = self.dll.AbortAcquisition() + return ERROR_DICT[error_code] + + def _shut_down(self): + error_code = self.dll.ShutDown() + return ERROR_DICT[error_code] + + def _start_acquisition(self): + error_code = self.dll.StartAcquisition() + self.dll.WaitForAcquisition() + return ERROR_DICT[error_code] + +# setter functions + + def _set_shutter(self, typ, mode, closingtime, openingtime): + """ + @param int typ: 0 Output TTL low signal to open shutter + 1 Output TTL high signal to open shutter + @param int mode: 0 Fully Auto + 1 Permanently Open + 2 Permanently Closed + 4 Open for FVB series + 5 Open for any series + """ + typ, mode, closingtime, openingtime = c_int(typ), c_int(mode), c_float(closingtime), c_float(openingtime) + error_code = self.dll.SetShutter(typ, mode, closingtime, openingtime) + + return ERROR_DICT[error_code] + + def _set_exposuretime(self, time): + """ + @param float time: exposure duration + @return string answer from the camera + """ + error_code = self.dll.SetExposureTime(c_float(time)) + return ERROR_DICT[error_code] + + def _set_read_mode(self, mode): + """ + @param string mode: string corresponding to certain ReadMode + @return string answer from the camera + """ + check_val = 0 + + if hasattr(ReadMode, mode): + n_mode = getattr(ReadMode, mode).value + n_mode = c_int(n_mode) + error_code = self.dll.SetReadMode(n_mode) + if mode == 'IMAGE': + self.log.debug("widt:{0}, height:{1}".format(self._width, self._height)) + msg = self._set_image(1, 1, 1, self._width, 1, self._height) + if msg != 'DRV_SUCCESS': + self.log.warning('{0}'.format(ERROR_DICT[error_code])) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.warning('Readmode was not set: {0}'.format(ERROR_DICT[error_code])) + check_val = -1 + else: + self._read_mode = mode + + return check_val + + def _set_trigger_mode(self, mode): + """ + @param string mode: string corresponding to certain TriggerMode + @return string: answer from the camera + """ + check_val = 0 + if hasattr(TriggerMode, mode): + n_mode = c_int(getattr(TriggerMode, mode).value) + self.log.debug('Input to function: {0}'.format(n_mode)) + error_code = self.dll.SetTriggerMode(n_mode) + else: + self.log.warning('{0} mode is not supported'.format(mode)) + check_val = -1 + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + check_val = -1 + else: + self._trigger_mode = mode + + return check_val + + def _set_image(self, hbin, vbin, hstart, hend, vstart, vend): + """ + This function will set the horizontal and vertical binning to be used when taking a full resolution image. + Parameters + @param int hbin: number of pixels to bin horizontally + @param int vbin: number of pixels to bin vertically. int hstart: Start column (inclusive) + @param int hend: End column (inclusive) + @param int vstart: Start row (inclusive) + @param int vend: End row (inclusive). + + @return string containing the status message returned by the function call + """ + hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin),\ + c_int(hstart), c_int(hend), c_int(vstart), c_int(vend) + + error_code = self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend) + msg = ERROR_DICT[error_code] + if msg == 'DRV_SUCCESS': + self._hbin = hbin.value + self._vbin = vbin.value + self._hstart = hstart.value + self._hend = hend.value + self._vstart = vstart.value + self._vend = vend.value + self._width = int((self._hend - self._hstart + 1) / self._hbin) + self._height = int((self._vend - self._vstart + 1) / self._vbin) + else: + self.log.error('Call to SetImage went wrong:{0}'.format(msg)) + return ERROR_DICT[error_code] + + def _set_output_amplifier(self, typ): + """ + @param c_int typ: 0: EMCCD gain, 1: Conventional CCD register + @return string: error code + """ + error_code = self.dll.SetOutputAmplifier(typ) + return ERROR_DICT[error_code] + + def _set_preamp_gain(self, index): + """ + @param c_int index: 0 - (Number of Preamp gains - 1) + """ + error_code = self.dll.SetPreAmpGain(index) + return ERROR_DICT[error_code] + + def _set_temperature(self, temp): + temp = c_int(temp) + error_code = self.dll.SetTemperature(temp) + return ERROR_DICT[error_code] + + def _set_acquisition_mode(self, mode): + """ + Function to set the acquisition mode + @param mode: + @return: + """ + check_val = 0 + if hasattr(AcquisitionMode, mode): + n_mode = c_int(getattr(AcquisitionMode, mode).value) + error_code = self.dll.SetAcquisitionMode(n_mode) + else: + self.log.warning('{0} mode is not supported'.format(mode)) + check_val = -1 + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + check_val = -1 + else: + self._acquisition_mode = mode + + return check_val + + def _set_cooler(self, state): + if state: + error_code = self.dll.CoolerON() + else: + error_code = self.dll.CoolerOFF() + + return ERROR_DICT[error_code] + + def _set_frame_transfer(self, bool): + acq_mode = self._acquisition_mode + + if (acq_mode == 'SINGLE_SCAN') | (acq_mode == 'KINETIC'): + self.log.debug('Setting of frame transfer mode has no effect in acquisition ' + 'mode \'SINGLE_SCAN\' or \'KINETIC\'.') + return -1 + else: + if bool: + rtrn_val = self.dll.SetFrameTransferMode(1) + else: + rtrn_val = self.dll.SetFrameTransferMode(0) + + if ERROR_DICT[rtrn_val] == 'DRV_SUCCESS': + return 0 + else: + self.log.warning('Could not set frame transfer mode:{0}'.format(ERROR_DICT[rtrn_val])) + return -1 + +# getter functions + def _get_status(self, status): + error_code = self.dll.GetStatus(byref(status)) + return ERROR_DICT[error_code] + + def _get_detector(self, nx_px, ny_px): + error_code = self.dll.GetDetector(byref(nx_px), byref(ny_px)) + return ERROR_DICT[error_code] + + def _get_camera_serialnumber(self, number): + """ + Gives serial number + Parameters + """ + error_code = self.dll.GetCameraSerialNumber(byref(number)) + return ERROR_DICT[error_code] + + def _get_acquisition_timings(self): + exposure = c_float() + accumulate = c_float() + kinetic = c_float() + error_code = self.dll.GetAcquisitionTimings(byref(exposure), + byref(accumulate), + byref(kinetic)) + self._exposure = exposure.value + self._accumulate = accumulate.value + self._kinetic = kinetic.value + return ERROR_DICT[error_code] + + def _get_oldest_image(self): + """ Return an array of last acquired image. + + @return numpy array: image data in format [[row],[row]...] + + Each pixel might be a float, integer or sub pixels + """ + + width = self._width + height = self._height + + if self._read_mode == 'IMAGE': + if self._acquisition_mode == 'SINGLE_SCAN': + dim = width * height / self._hbin / self._vbin + elif self._acquisition_mode == 'KINETICS': + dim = width * height / self._hbin / self._vbin * self._scans + elif self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'FVB': + if self._acquisition_mode == 'SINGLE_SCAN': + dim = width + elif self._acquisition_mode == 'KINETICS': + dim = width * self._scans + + dim = int(dim) + image_array = np.zeros(dim) + cimage_array = c_int * dim + cimage = cimage_array() + error_code = self.dll.GetOldestImage(pointer(cimage), dim) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.warning('Couldn\'t retrieve an image') + else: + self.log.debug('image length {0}'.format(len(cimage))) + for i in range(len(cimage)): + # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode + image_array[i] = cimage[i] + + image_array = np.reshape(image_array, (int(self._width/self._hbin), int(self._height/self._vbin))) + return image_array + + def _get_number_amp(self): + """ + @return int: Number of amplifiers available + """ + n_amps = c_int() + self.dll.GetNumberAmp(byref(n_amps)) + return n_amps.value + + def _get_number_preamp_gains(self): + """ + Number of gain settings available for the pre amplifier + + @return int: Number of gains available + """ + n_gains = c_int() + self.dll.GetNumberPreAmpGains(byref(n_gains)) + return n_gains.value + + def _get_preamp_gain(self): + """ + Function returning + @return tuple (int1, int2): First int describing the gain setting, second value the actual gain + """ + index = c_int() + gain = c_float() + self.dll.GetPreAmpGain(index, byref(gain)) + return index.value, gain.value + + def _get_temperature(self): + temp = c_int() + error_code = self.dll.GetTemperature(byref(temp)) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.error('Can not retrieve temperature'.format(ERROR_DICT[error_code])) + return temp.value + + def _get_temperature_f(self): + """ + Status of the cooling process + current temperature + @return: (float, str) containing current temperature and state of the cooling process + """ + temp = c_float() + error_code = self.dll.GetTemperatureF(byref(temp)) + + return temp.value, ERROR_DICT[error_code] + + def _get_size_of_circular_ring_buffer(self): + index = c_long() + error_code = self.dll.GetSizeOfCircularBuffer(byref(index)) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.error('Can not retrieve size of circular ring ' + 'buffer: {0}'.format(ERROR_DICT[error_code])) + return index.value + + def _get_number_new_images(self): + first = c_long() + last = c_long() + error_code = self.dll.GetNumberNewImages(byref(first), byref(last)) + msg = ERROR_DICT[error_code] + pass_returns = ['DRV_SUCCESS', 'DRV_NO_NEW_DATA'] + if msg not in pass_returns: + self.log.error('Can not retrieve number of new images {0}'.format(ERROR_DICT[error_code])) + + return first.value, last.value + + # not working properly (only for n_scans = 1) + def _get_images(self, first_img, last_img, n_scans): + """ Return an array of last acquired image. + + @return numpy array: image data in format [[row],[row]...] + + Each pixel might be a float, integer or sub pixels + """ + + width = self._width + height = self._height + + # first_img, last_img = self._get_number_new_images() + # n_scans = last_img - first_img + dim = width * height * n_scans + + dim = int(dim) + image_array = np.zeros(dim) + cimage_array = c_int * dim + cimage = cimage_array() + + first_img = c_long(first_img) + last_img = c_long(last_img) + size = c_ulong(width * height) + val_first = c_long() + val_last = c_long() + error_code = self.dll.GetImages(first_img, last_img, pointer(cimage), + size, byref(val_first), byref(val_last)) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) + else: + for i in range(len(cimage)): + # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode + image_array[i] = cimage[i] + + self._cur_image = image_array + return image_array +# non interface functions regarding setpoint interface From 76d7761977e41cac2f8cf55673fc18a4ef96abde Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 8 Apr 2020 19:54:29 +0200 Subject: [PATCH 03/49] updating of the camera interface I try to determine which functions are needed in our logic module on the basis than the current camera interface do not offer enough functions for enough practival uses. --- hardware/camera/andor/Newton_940.py | 3 +- interface/camera_complete_interface.py | 194 +++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 interface/camera_complete_interface.py diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 6879243aab..8d5af0da56 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -28,7 +28,8 @@ from ctypes import * import numpy as np -from core.module import Base, ConfigOption +from core.module import Base +from core.configoption import ConfigOption from interface.camera_interface import CameraInterface diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py new file mode 100644 index 0000000000..66e9ce0bc4 --- /dev/null +++ b/interface/camera_complete_interface.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- + +""" +This file contains the updated Qudi Interface for a camera. + + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +from core.interface import abstract_interface_method +from core.meta import InterfaceMetaclass + + +class CameraInterface(metaclass=InterfaceMetaclass): + """ This interface is used to manage and visualize a simple camera + """ + + ############################################################################## + # Basic functions + ############################################################################## + + @abstract_interface_method + def get_name(self): + """ Retrieve an identifier of the camera that the GUI can print + + @return string: name for the camera + """ + pass + + @abstract_interface_method + def get_size(self): + """ Retrieve size of the image in pixel + + @return tuple: Size (width, height) + """ + pass + + @abstract_interface_method + def start_acquisition(self): + """ Start a single acquisition + + @return bool: Success ? + """ + pass + + @abstract_interface_method + def stop_acquisition(self): + """ Stop/abort live or single acquisition + + @return bool: Success ? + """ + pass + + @abstract_interface_method + def get_acquired_data(self): + """ Return an array of last acquired image. + + @return numpy array: image data in format [[row],[row]...] + + Each pixel might be a float, integer or sub pixels + """ + pass + + + @abstract_interface_method + def get_ready_state(self): + """ Is the camera ready for an acquisition ? + + @return bool: ready ? + """ + pass + + ############################################################################## + # Read mode functions + ############################################################################## + + @abstract_interface_method + def get_read_mode(self): + """ + Getter method returning the current read mode used by the camera. + + :return: @str read mode (must be compared to a dict) + """ + pass + + @abstract_interface_method + def set_read_mode(self, read_mode, **kwargs): + """ + Setter method setting the read mode used by the camera. + + :param read_mode: @str read mode (must be compared to a dict) + :param kwargs: packed @dict which contain a series of arguments used by the differents read modes + :return: nothing + """ + pass + + ############################################################################## + # Acquisition mode functions + ############################################################################## + + @abstract_interface_method + def get_acquisition_mode(self): + """ + Getter method returning the current acquisition mode used by the camera. + + :return: @str acquisition mode (must be compared to a dict) + """ + pass + + @abstract_interface_method + def set_acquisition_mode(self, acquisition_mode, **kwargs): + """ + Setter method setting the acquisition mode used by the camera. + + :param read_mode: @str read mode (must be compared to a dict) + :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes + :return: nothing + """ + pass + + @abstract_interface_method + def get_exposure(self): + """ Get the exposure time in seconds + + @return float exposure time + """ + pass + + @abstract_interface_method + def set_exposure(self, exposure): + """ Set the exposure time in seconds + + @param float time: desired new exposure time + + @return float: setted new exposure time + """ + pass + + @abstract_interface_method + def get_gain(self): + """ Get the gain + + @return float: exposure gain + """ + pass + + @abstract_interface_method + def set_gain(self, gain): + """ Set the gain + + @param float gain: desired new gain + + @return float: new exposure gain + """ + pass + + ############################################################################## + # Trigger mode functions + ############################################################################## + + @abstract_interface_method + def get_trigger_mode(self): + """ + Getter method returning the current trigger mode used by the camera. + + :return: @str trigger mode (must be compared to a dict) + """ + pass + + @abstract_interface_method + def set_trigger_mode(self, trigger_mode, **kwargs): + """ + Setter method setting the trigger mode used by the camera. + + :param trigger_mode: @str trigger mode (must be compared to a dict) + :param kwargs: packed @dict which contain a series of arguments specific to the differents trigger modes + :return: nothing + """ + pass + From cfec3e0a8914fa631e36cef1c919ea788677ab44 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 8 Apr 2020 19:56:49 +0200 Subject: [PATCH 04/49] Spectrum logic update add comments --- logic/spectrum_logic.py | 132 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 24c9cd770a..45f0fa263f 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -199,6 +199,11 @@ def image_data(self): @property def grating(self): + """ + Getter method returning the grating number used by the spectrometer. + + :return: @int active grating number or 0 if error + """ grating_number = self.spectrometer_device.get_grating() is_int = isinstance(grating_number, int) if is_int: @@ -211,6 +216,12 @@ def grating(self): @grating.setter def grating(self, grating_number): + """ + Setter method setting the grating number to use by the spectrometer. + + :param grating_number: @int gating number to set active + :return: nothing + """ number_gratings = self.spectrometer_device.get_number_gratings() is_int = isinstance(grating_number, int) is_in_range = 0 < grating_number < number_gratings @@ -230,6 +241,12 @@ def grating(self, grating_number): @property def grating_offset(self, grating_number): + """ + Getter method returning the grating offset of the grating selected by the grating_number parameter. + + :param grating_number: @int grating number which correspond the offset + :return: @int the corresponding grating offset or 0 if error + """ number_gratings = self.spectrometer_device.get_number_gratings() var_is_int = isinstance(grating_number, int) var_is_in_range = 0 < grating_number < number_gratings @@ -255,6 +272,13 @@ def grating_offset(self, grating_number): @grating_offset.setter def grating_offset(self, grating_number, grating_offset): + """ + Setter method setting the grating offset of the grating selected by the grating_number parameter. + + :param grating_number: @int grating number which correspond the offset + :param grating_offset: @int grating offset + :return: nothing + """ number_gratings = self.spectrometer_device.get_number_gratings() grating_is_int = isinstance(grating_number, int) grating_is_in_range = -1 < grating_number < number_gratings @@ -293,6 +317,11 @@ def grating_offset(self, grating_number, grating_offset): @property def center_wavelength(self): + """ + Getter method returning the center wavelength of the measured spectral range. + + :return: @float the spectrum center wavelength or 0 if error + """ wavelength = self.spectrometer_device.get_wavelength() is_float = isinstance(wavelength, float) if is_float: @@ -304,6 +333,12 @@ def center_wavelength(self): @center_wavelength.setter def center_wavelength(self, wavelength): + """ + Setter method setting the center wavelength of the measured spectral range. + + :param wavelength: @float center wavelength + :return: nothing + """ wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) is_float = isinstance(wavelength, float) is_in_range = wavelength_min < wavelength < wavelength_max @@ -323,6 +358,12 @@ def center_wavelength(self, wavelength): @property def wavelength_range(self): + """ + Getter method returning the wavelength array of the full measured spectral range. + (used for plotting spectrum with the spectral range) + + :return: @ndarray measured wavelength array or 0 if error + """ wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) wavelength_range = self.spectrometer_device.get_calibration(self._number_of_pixels) is_ndarray = isinstance(wavelength_range, np.ndarray) @@ -340,6 +381,12 @@ def wavelength_range(self): @property def number_of_pixels(self): + """ + Getter method returning the number of pixels used by the spectrometer DLLs calibration function. + (the value return by this function must match with the real pixel number of the camera) + + :return: @int number of pixels or 0 if error + """ number_pixels = self.spectrometer_device.get_number_of_pixels() is_int = isinstance(number_pixels, int) is_positive = 0 < number_pixels @@ -356,6 +403,13 @@ def number_of_pixels(self): @number_of_pixels.setter def number_of_pixels(self, number_pixels): + """ + Setter method setting the number of pixels used by the spectrometer DLLs calibration function. + (the value set by this function must be the real pixel number of the camera) + + :param number_pixels: @int number of pixels + :return: nothing + """ is_int = isinstance(number_pixels, int) is_positive = 0 < number_pixels is_change = number_pixels != self._number_of_pixels @@ -373,6 +427,12 @@ def number_of_pixels(self, number_pixels): @property def pixel_width(self): + """ + Getter method returning the pixel width used by the spectrometer DLLs calibration function. + (the value returned by this function must match the real pixel width of the camera) + + :return: @int pixel width or 0 if error + """ pixel_width = self.spectrometer_device.get_pixel_width() is_float = isinstance(pixel_width, float) is_positive = 0 < pixel_width @@ -389,6 +449,13 @@ def pixel_width(self): @pixel_width.setter def pixel_width(self, pixel_width): + """ + Setter method setting the pixel width used by the spectrometer DLLs calibration function. + (the value set by this function must be the real pixel width of the camera) + + :param pixel_width: @int pixel width + :return: nothing + """ is_float = isinstance(pixel_width, float) is_positive = 0 < pixel_width is_change = pixel_width != self._pixel_width @@ -410,6 +477,12 @@ def pixel_width(self, pixel_width): @property def detector_offset(self): + """ + Getter method returning the detector offset used by the spectrometer DLLs calibration function. + (the value returned by this function must match the real detector offset value of the camera) + + :return: @int detector offset or 0 error + """ offset = self.spectrometer_device.get_detector_offset() is_int = isinstance(offset, int) if is_int: @@ -421,6 +494,13 @@ def detector_offset(self): @detector_offset.setter def detector_offset(self, detector_offset): + """ + Setter method returning the detector offset used by the spectrometer DLLs calibration function. + (the value returned by this function must be the real detector offset value of the camera) + + :param detector_offset: @int detetcor offset + :return: nothing + """ number_pixels = 514 #TODO : add the Newton funtion returning the number of pixels (Hardcoding) offset_min = -number_pixels//2 - 1 offset_max = number_pixels//2 @@ -446,6 +526,11 @@ def detector_offset(self, detector_offset): @property def input_port(self): + """ + Getter method returning the active current input port of the spectrometer. + + :return: @int active input port (0 front and 1 side) or 0 if error + """ input_port = self.spectrometer_device.get_input_port() is_int = isinstance(input_port, int) is_in_range = -1 < input_port < 2 @@ -461,6 +546,12 @@ def input_port(self): @input_port.setter def input_port(self, input_port): + """ + Setter method setting the active current input port of the spectrometer. + + :param input_port: input port + :return: nothing + """ side_port_possible = self.spectrometer_device.flipper_mirror_is_present(1) is_int = isinstance(input_port, int) is_in_range = -1 < input_port < 2 @@ -482,6 +573,11 @@ def input_port(self, input_port): @property def output_port(self): + """ + Getter method returning the active current output port of the spectrometer. + + :return: @int active output port (0 front and 1 side) or 0 if error + """ output_port = self.spectrometer_device.get_output_port() is_int = isinstance(output_port, int) is_in_range = -1 < output_port < 2 @@ -497,6 +593,12 @@ def output_port(self): @output_port.setter def output_port(self, output_port): + """ + Setter method setting the active current output port of the spectrometer. + + :param output_port: output port + :return: nothing + """ side_port_possible = self.spectrometer_device.flipper_mirror_is_present(2) is_int = isinstance(output_port, int) is_in_range = -1 < output_port < 2 @@ -518,6 +620,11 @@ def output_port(self, output_port): @property def input_slit_width(self): + """ + Getter method returning the active input port slit width of the spectrometer. + + :return: @float input port slit width or 0 if error + """ slit_width = self.spectrometer_device.get_auto_slit_width('input', self._input_port) is_float = isinstance(slit_width, float) if is_float: @@ -529,6 +636,12 @@ def input_slit_width(self): @input_slit_width.setter def input_slit_width(self, slit_width): + """ + Setter method setting the active input port slit width of the spectrometer. + + :param slit_width: @float input port slit width + :return: nothing + """ slit_is_present = self.spectrometer_device.auto_slit_is_present('input', self._input_port) is_float = isinstance(slit_width, float) if is_float: @@ -544,6 +657,11 @@ def input_slit_width(self, slit_width): @property def output_slit_width(self): + """ + Getter method returning the active output port slit width of the spectrometer. + + :return: @float output port slit width or 0 if error + """ slit_width = self.spectrometer_device.get_auto_slit_width('output', self._output_port) is_float = isinstance(slit_width, float) if is_float: @@ -554,6 +672,12 @@ def output_slit_width(self): @output_slit_width.setter def output_slit_width(self, slit_width): + """ + Setter method setting the active output port slit width of the spectrometer. + + :param slit_width: @float output port slit width + :return: nothing + """ slit_is_present = self.spectrometer_device.auto_slit_is_present('output', self._output_port) is_float = isinstance(slit_width, float) if is_float: @@ -569,6 +693,12 @@ def output_slit_width(self, slit_width): ############################################################################## # Camera functions ############################################################################## + # All functions defined in this part should be used to + # + # + ############################################################################## + # Gratings functions + ############################################################################## - def set_read_mode(self, mode): + def read_mode(self, mode): self.camera_device.set_read_mode(mode) From 9264da888c99a24db1962ab5480da2710734637e Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 9 Apr 2020 15:50:43 +0200 Subject: [PATCH 05/49] Camera complete interface update New function less dict arguments --- interface/camera_complete_interface.py | 119 +++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 66e9ce0bc4..d01a338b82 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -122,7 +122,7 @@ def get_acquisition_mode(self): pass @abstract_interface_method - def set_acquisition_mode(self, acquisition_mode, **kwargs): + def set_acquisition_mode(self, acquisition_mode): """ Setter method setting the acquisition mode used by the camera. @@ -133,7 +133,49 @@ def set_acquisition_mode(self, acquisition_mode, **kwargs): pass @abstract_interface_method - def get_exposure(self): + def get_accumulation_time(self): + """ + Getter method returning the accumulation cycle time scan carry out during an accumulate acquisition mode + by the camera. + + :return: @int accumulation cycle time or 0 if error + """ + pass + + @abstract_interface_method + def get_accumulation_time(self, accumulation_time): + """ + Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode + by the camera. + + :param accumulation_time: @int accumulation cycle time + :return: nothing + """ + pass + + @abstract_interface_method + def get_number_accumulated_scan(self): + """ + Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :return: @int number of accumulated scan or 0 if error + """ + pass + + @abstract_interface_method + def set_number_accumulated_scan(self, number_scan): + """ + Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :param number_scan: @int number of accumulated scan + :return: nothing + """ + pass + + @abstract_interface_method + def get_exposure_time(self): """ Get the exposure time in seconds @return float exposure time @@ -141,7 +183,7 @@ def get_exposure(self): pass @abstract_interface_method - def set_exposure(self, exposure): + def set_exposure_time(self, exposure_time): """ Set the exposure time in seconds @param float time: desired new exposure time @@ -182,13 +224,80 @@ def get_trigger_mode(self): pass @abstract_interface_method - def set_trigger_mode(self, trigger_mode, **kwargs): + def set_trigger_mode(self, trigger_mode): """ Setter method setting the trigger mode used by the camera. :param trigger_mode: @str trigger mode (must be compared to a dict) - :param kwargs: packed @dict which contain a series of arguments specific to the differents trigger modes :return: nothing """ pass + ############################################################################## + # Shutter mode functions + ############################################################################## + + @abstract_interface_method + def get_shutter_mode(self): + """ + Getter method returning the shutter mode and all its related parameters. + + :return: @tuple (@str shutter mode, @bool TTL low, @float closing time, @float opening time) + """ + pass + + @abstract_interface_method + def set_shutter_mode(self, shutter_mode, closing_time, opening_mode, TTL_low = True): + """ + Setter method setting the shutter mode and all its related parameters. + + :param shutter_mode: @str shutter mode + :param closing_time: @float closing time + :param opening_mode: @float opening time + :param TTL_low: @bool TTL low ? + :return: nothing + """ + pass + + ############################################################################## + # Temperature functions + ############################################################################## + + @abstract_interface_method + def get_cooler_status(self): + """ + Getter method returning the cooler status if ON or OFF. + + :return: @bool True if ON or False if OFF or 0 if error + """ + pass + + @abstract_interface_method + def set_cooler_status(self, cooler_ON = True): + """ + Setter method setting the cooler status ON or OFF of the camera. + + :param cooler_ON: @bool True if ON or False if OFF + :return: nothing + """ + pass + + + @abstract_interface_method + def get_temperature(self): + """ + Getter method returning the temperature of the camera. + + :return: @float temperature or 0 if error + """ + pass + + @abstract_interface_method + def set_temperature(self, temperature): + """ + Getter method returning the temperature of the camera. + + :param temperature: @float temperature or 0 if error + :return: nothing + """ + pass \ No newline at end of file From a3c77d7ed6b70c761611dd7c7e1456a950385483 Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 9 Apr 2020 16:49:23 +0200 Subject: [PATCH 06/49] Camera complete interface update 2 read mode parameters functions change --- interface/camera_complete_interface.py | 49 ++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index d01a338b82..338249489f 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -98,12 +98,55 @@ def get_read_mode(self): pass @abstract_interface_method - def set_read_mode(self, read_mode, **kwargs): + def set_read_mode(self, read_mode): """ Setter method setting the read mode used by the camera. :param read_mode: @str read mode (must be compared to a dict) - :param kwargs: packed @dict which contain a series of arguments used by the differents read modes + :return: nothing + """ + pass + + @abstract_interface_method + def get_track_parameters(self): + """ + Getter method returning the read mode tracks parameters of the camera. + + :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error + """ + pass + + @abstract_interface_method + def set_track_parameters(self, number_of_track, track_heigth, track_offset): + """ + Setter method setting the read mode tracks parameters of the camera. + + :param number_of_track: @int number of track + :param track_heigth: @int track height + :param track_offset: @int track offset + :return: nothing + """ + pass + + @abstract_interface_method + def get_image_parameters(self): + """ + Getter method returning the read mode image parameters of the camera. + + :return: @tuple (@int pixel height, @int pixel width, @tuple (@int start raw, @int end raw), + @tuple (@int start column, @int end column)) or 0 if error + """ + pass + + @abstract_interface_method + def set_image_parameters(self, pixel_height, pixel_width, raw_range, column_range): + """ + Setter method setting the read mode image parameters of the camera. + + :param pixel_height: @int pixel height + :param pixel_width: @int pixel width + :param raw_range: @tuple (@int start raw, @int end raw) + :param column_range: @tuple (@int start column, @int end column) :return: nothing """ pass @@ -143,7 +186,7 @@ def get_accumulation_time(self): pass @abstract_interface_method - def get_accumulation_time(self, accumulation_time): + def set_accumulation_time(self, accumulation_time): """ Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode by the camera. From 325f4cc7f9cce45271b12715ab63af906acdedc3 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 Apr 2020 10:23:38 +0200 Subject: [PATCH 07/49] Camera complete interface update 3 New functions name --- interface/camera_complete_interface.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 338249489f..1bb8d6140c 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -42,7 +42,7 @@ def get_name(self): pass @abstract_interface_method - def get_size(self): + def get_image_size(self): """ Retrieve size of the image in pixel @return tuple: Size (width, height) @@ -281,23 +281,20 @@ def set_trigger_mode(self, trigger_mode): ############################################################################## @abstract_interface_method - def get_shutter_mode(self): + def shutter_is_open(self): """ - Getter method returning the shutter mode and all its related parameters. + Getter method returning if the shutter is open. - :return: @tuple (@str shutter mode, @bool TTL low, @float closing time, @float opening time) + :return: @bool shutter open ? """ pass @abstract_interface_method - def set_shutter_mode(self, shutter_mode, closing_time, opening_mode, TTL_low = True): + def shutter_is_open(self, shutter_open): """ - Setter method setting the shutter mode and all its related parameters. + Setter method setting if the shutter is open. - :param shutter_mode: @str shutter mode - :param closing_time: @float closing time - :param opening_mode: @float opening time - :param TTL_low: @bool TTL low ? + :param shutter_mode: @bool shutter open :return: nothing """ pass @@ -307,7 +304,7 @@ def set_shutter_mode(self, shutter_mode, closing_time, opening_mode, TTL_low = T ############################################################################## @abstract_interface_method - def get_cooler_status(self): + def cooler_ON(self): """ Getter method returning the cooler status if ON or OFF. @@ -316,11 +313,11 @@ def get_cooler_status(self): pass @abstract_interface_method - def set_cooler_status(self, cooler_ON = True): + def cooler_ON(self, cooler_ON): """ - Setter method setting the cooler status ON or OFF of the camera. + Getter method returning the cooler status if ON or OFF. - :param cooler_ON: @bool True if ON or False if OFF + :cooler_ON: @bool True if ON or False if OFF :return: nothing """ pass From afca522208a7e7e3cfff3fc04529c3189b64061c Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 Apr 2020 10:37:17 +0200 Subject: [PATCH 08/49] Logic module in progress camera method added (+decorator) --- logic/spectrum_logic.py | 387 ++++++++++++++++++++++++++++++++++------ 1 file changed, 333 insertions(+), 54 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 45f0fa263f..7dc0cbb197 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -32,6 +32,7 @@ from logic.generic_logic import GenericLogic from core.configoption import ConfigOption +from time import sleep from datetime import date @@ -44,30 +45,32 @@ class SpectrumLogic(GenericLogic): camera = Connector(interface='CameraInterface') savelogic = Connector(interface='SaveLogic', optional=True) - # declare status variables + # declare status variables (logic attribute) : _spectrum_data = StatusVar('spectrum_data', np.empty((2, 0))) _background_data = StatusVar('background_data', np.empty((2, 0))) _image_data = StatusVar('image_data', np.empty((2, 0))) - _grating = StatusVar('grating', 1) - _grating_offset = StatusVar('grating_offset', 0) - - _center_wavelength = StatusVar('center_wavelength', 400) - _wavelength_range = StatusVar('wavelength_range', np.empty((2, 0))) - _number_of_pixels = StatusVar('number_of_pixels', 512) - _pixel_width = StatusVar('pixel_width', 1e-4) - - _detector_offset = StatusVar('detector_offset', 0) - - _input_port = StatusVar('input_port', 1) - _output_port = StatusVar('output_port', 0) - _input_slit_width = StatusVar('input_slit_width', 100) - _output_slit_width = StatusVar('output_slit_width', 100) - # Allow to set a default save directory in the config file : _default_save_file_path = ConfigOption('default_save_file_path') _save_file_path = StatusVar('save_file_path', _default_save_file_path) + # declare status variables (camera attribute) : + _read_mode = StatusVar('read_mode', 'FVB') + _number_of_track = StatusVar('number_of_track', 1) + _track_height = StatusVar('track_height', 50) + _track_offset = StatusVar('track_offset', 0) + _pixel_height = StatusVar('pixel_height', 1e-4) + _pixel_width = StatusVar('pixel_width', 1e-4) + + _acquistion_mode = StatusVar('acquistion_mode', 'SINGLESCAN') + _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) + _accumulation_time = StatusVar('number_accumulated_scan', 1e-3) + _number_of_scan = StatusVar('number_of_scan', 1) + _scan_delay = StatusVar('scan_delay', 1e-2) + _exposure_time = StatusVar('exposure_time', 1e-4) + _camera_gain = StatusVar('camera_gain', 1) + + _shutter_mode = StatusVar('shutter_mode', (1, 1e-4, 1e-4, True)) ############################################################################## # Basic functions ############################################################################## @@ -88,6 +91,23 @@ def on_activate(self): self.spectrometer_device = self.spectrometer() self.camera_device = self.camera() + # declare spectrometer attributes : + self._grating = self.grating + self._grating_offset = self.grating_offset + + self._center_wavelength = self.center_wavelength + self._wavelength_range = self.wavelength_range + + self._detector_offset = self.detector_offset + + self._input_port = self.input_port + self._output_port = self.output_port + self._input_slit_width = self.input_slit_width + self._output_slit_width = self.output_slit_width + + # declare camera attributes : + self._camera_size = self.camera_device.size() + self._save_logic = self.savelogic() def on_deactivate(self): @@ -138,42 +158,35 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): # Acquisition functions ############################################################################## - def start_acquisition(self): - """ - Method proceeding to data acquisition. - - :return: 1 if succeed or 0 if failed - """ - - if self.camera_device.start_single_acquisition(): - data = self.camera_device.get_acquired_data() - self.log.info('Single spectrum acquisition achieved with success ') - return data - else: - self.log.info('Single spectrum acquisition aborted ') - return 0 - - def stop_acquisition(self): - """ - Method aborting data acquisition and all the related procedures - :return: 1 if succeed or 0 if failed - """ - - if self.camera_device.stop_acquisition(): - self.log.info('Acquisition aborted with success ') - return 1 - else: - self.log.error('Acquisition can\'t be aborted : try again or disconnect the device ') - return 0 - def acquire_spectrum(self): - pass + for i in range(0, self._number_of_scan): + self.camera_device.start_acquisition() + self._spectrum_data[i] = self.camera_device.get_acquired_data() + sleep(self._scan_delay) + self.log.info("Spectrum acquisition succeed ! Number of acquired scan : {} " + "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) def acquire_background(self): - pass + if self.get_shutter_mode()[0] != 0: + self.set_shutter_mode(0) + for i in range(0, self._number_of_scan): + self.camera_device.start_acquisition() + self._background_data[i] = self.camera_device.get_acquired_data() + sleep(self._scan_delay) + self.log.info("Background acquisition succeed ! Number of acquired scan : {} " + "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + self.set_shutter_mode(*self._shutter_mode) def acquire_image(self): - pass + if self.get_shutter_mode()[0] != 0: + self.set_shutter_mode(0) + for i in range(0, self._number_of_scan): + self.camera_device.start_acquisition() + self._background_data[i] = self.camera_device.get_acquired_data() + sleep(self._scan_delay) + self.log.info("Background acquisition succeed ! Number of acquired scan : {} " + "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + self.set_shutter_mode(*self._shutter_mode) @property def spectrum_data(self): @@ -365,7 +378,7 @@ def wavelength_range(self): :return: @ndarray measured wavelength array or 0 if error """ wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) - wavelength_range = self.spectrometer_device.get_calibration(self._number_of_pixels) + wavelength_range = self.spectrometer_device.get_calibration(self._pixel_matrix_dimension[0]) is_ndarray = isinstance(wavelength_range, np.ndarray) is_in_range = np.min(wavelength_range) > wavelength_min and np.max(wavelength_range) > wavelength_max if is_ndarray and is_in_range: @@ -391,7 +404,7 @@ def number_of_pixels(self): is_int = isinstance(number_pixels, int) is_positive = 0 < number_pixels if is_int and is_positive: - self._number_of_pixels = number_pixels + self._pixel_matrix_dimension[0] = number_pixels return number_pixels else: if is_int: @@ -412,11 +425,11 @@ def number_of_pixels(self, number_pixels): """ is_int = isinstance(number_pixels, int) is_positive = 0 < number_pixels - is_change = number_pixels != self._number_of_pixels + is_change = number_pixels != self._pixel_matrix_dimension[0] if is_int and is_positive and is_change: self.spectrometer_device.get_pixel_width(number_pixels) self.log.info('Number of pixels has been changed correctly ') - self._number_of_pixels = number_pixels + self._pixel_matrix_dimension[0] = number_pixels else: if is_int: self.log.debug('Number of pixels parameter is not correct : it must be a int ') @@ -697,8 +710,274 @@ def output_slit_width(self, slit_width): # # ############################################################################## - # Gratings functions + # Basic functions + ############################################################################## + + @property + def acquired_data(self): + """ Return an array of last acquired image. + + @return numpy array: image data in format [[row],[row]...] + Each pixel might be a float, integer or sub pixels + """ + + @property + def ready_state(self): + """ Is the camera ready for an acquisition ? + + @return bool: ready ? + """ + pass + + ############################################################################## + # Read mode functions + ############################################################################## + + @property + def read_mode(self): + """ + Getter method returning the current read mode used by the camera. + + :return: @str read mode (must be compared to a dict) + """ + pass + + @read_mode.setter + def read_mode(self, read_mode): + """ + Setter method setting the read mode used by the camera. + + :param read_mode: @str read mode (must be compared to a dict) + :return: nothing + """ + pass + + def get_track_parameters(self): + """ + Getter method returning the read mode tracks parameters of the camera. + + :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error + """ + pass + + def set_track_parameters(self, number_of_track, track_heigth, track_offset): + """ + Setter method setting the read mode tracks parameters of the camera. + + :param number_of_track: @int number of track + :param track_heigth: @int track height + :param track_offset: @int track offset + :return: nothing + """ + pass + + def get_image_parameters(self): + """ + Getter method returning the read mode image parameters of the camera. + + :return: @tuple (@int pixel height, @int pixel width, @tuple (@int start raw, @int end raw), + @tuple (@int start column, @int end column)) or 0 if error + """ + pass + + def set_image_parameters(self, pixel_height, pixel_width, raw_range, column_range): + """ + Setter method setting the read mode image parameters of the camera. + + :param pixel_height: @int pixel height + :param pixel_width: @int pixel width + :param raw_range: @tuple (@int start raw, @int end raw) + :param column_range: @tuple (@int start column, @int end column) + :return: nothing + """ + pass + + ############################################################################## + # Acquisition mode functions ############################################################################## - def read_mode(self, mode): - self.camera_device.set_read_mode(mode) + @property + def acquisition_mode(self): + """ + Getter method returning the current acquisition mode used by the camera. + + :return: @str acquisition mode (must be compared to a dict) + """ + pass + + @acquisition_mode.setter + def acquisition_mode(self, acquisition_mode): + """ + Setter method setting the acquisition mode used by the camera. + + :param read_mode: @str read mode (must be compared to a dict) + :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes + :return: nothing + """ + pass + + @property + def accumulation_time(self): + """ + Getter method returning the accumulation cycle time scan carry out during an accumulate acquisition mode + by the camera. + + :return: @int accumulation cycle time or 0 if error + """ + pass + + @accumulation_time.setter + def accumulation_time(self, accumulation_time): + """ + Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode + by the camera. + + :param accumulation_time: @int accumulation cycle time + :return: nothing + """ + pass + + @property + def number_accumulated_scan(self): + """ + Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :return: @int number of accumulated scan or 0 if error + """ + pass + + @number_accumulated_scan.setter + def number_accumulated_scan(self, number_scan): + """ + Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :param number_scan: @int number of accumulated scan + :return: nothing + """ + pass + + @property + def exposure_time(self): + """ Get the exposure time in seconds + + @return float exposure time + """ + pass + + @exposure_time.setter + def exposure_time(self, exposure_time): + """ Set the exposure time in seconds + + @param float time: desired new exposure time + + @return float: setted new exposure time + """ + pass + + @property + def camera_gain(self): + """ Get the gain + + @return float: exposure gain + """ + pass + + @camera_gain.setter + def camera_gain(self, gain): + """ Set the gain + + @param float gain: desired new gain + + @return float: new exposure gain + """ + pass + + ############################################################################## + # Trigger mode functions + ############################################################################## + + @property + def trigger_mode(self): + """ + Getter method returning the current trigger mode used by the camera. + + :return: @str trigger mode (must be compared to a dict) + """ + pass + + @trigger_mode.setter + def trigger_mode(self, trigger_mode): + """ + Setter method setting the trigger mode used by the camera. + + :param trigger_mode: @str trigger mode (must be compared to a dict) + :return: nothing + """ + pass + + ############################################################################## + # Shutter mode functions + ############################################################################## + + @property + def shutter_is_open(self): + """ + Getter method returning if the shutter is open. + + :return: @bool shutter open ? + """ + pass + + @shutter_is_open.setter + def shutter_is_open(self, shutter_open): + """ + Setter method setting if the shutter is open. + + :param shutter_mode: @bool shutter open + :return: nothing + """ + pass + + ############################################################################## + # Temperature functions + ############################################################################## + + @property + def cooler_ON(self): + """ + Getter method returning the cooler status if ON or OFF. + + :return: @bool True if ON or False if OFF or 0 if error + """ + pass + + @cooler_ON.setter + def cooler_ON(self, cooler_ON): + """ + Getter method returning the cooler status if ON or OFF. + + :cooler_ON: @bool True if ON or False if OFF + :return: nothing + """ + pass + + @property + def temperature(self): + """ + Getter method returning the temperature of the camera. + + :return: @float temperature or 0 if error + """ + pass + + @temperature.setter + def temperature(self, temperature): + """ + Getter method returning the temperature of the camera. + + :param temperature: @float temperature or 0 if error + :return: nothing + """ + pass \ No newline at end of file From e371764d250006261e4cc5d3f6e540586643ad8b Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Fri, 10 Apr 2020 15:12:49 +0200 Subject: [PATCH 09/49] =?UTF-8?q?camera=20complete=20interface=20update4?= =?UTF-8?q?=20+=20Newton940=20d=C3=A9but=20de=20r=C3=A9=C3=A9criture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hardware/camera/andor/Newton_940.py | 234 ++++++++++++++++++------- interface/camera_complete_interface.py | 8 + 2 files changed, 180 insertions(+), 62 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 8d5af0da56..6d39e20a73 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -27,12 +27,12 @@ from enum import Enum from ctypes import * import numpy as np +import ctypes as ct from core.module import Base from core.configoption import ConfigOption - -from interface.camera_interface import CameraInterface +from interface.camera_complete_interface import CameraInterface class ReadMode(Enum): @@ -103,7 +103,6 @@ class Newton940(Base, CameraInterface): """ Hardware class for Andors Newton940 """ - _modtype = 'camera' _modclass = 'hardware' @@ -113,9 +112,13 @@ class Newton940(Base, CameraInterface): _default_cooler_on = ConfigOption('default_cooler_on', True) _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') - #_dll_location = ConfigOption('dll_location', missing='error') + _dll_location = ConfigOption('dll_location', missing='error') #_dll_location = 'ATMCD32D.dll' + _camera_name = 'Newton940' + + + _exposure = _default_exposure _temperature = _default_temperature _cooler_on = _default_cooler_on @@ -126,55 +129,196 @@ class Newton940(Base, CameraInterface): _height = 0 _last_acquisition_mode = None # useful if config changes during acq _supported_read_mode = ReadMode # TODO: read this from camera, all readmodes are available for iXon Ultra - _max_cooling = -100 + _max_cooling = -85 _live = False - _camera_name = 'iXon Ultra 897' _shutter = "closed" _trigger_mode = _default_trigger_mode _scans = 1 #TODO get from camera _acquiring = False +################################################################################################### +# Basic module activation/deactivation +################################################################################################### def on_activate(self): - """ Initialisation performed during activation of the module. - """ - # self.cam.SetAcquisitionMode(1) # single - # self.cam.SetTriggerMode(0) # internal - # self.cam.SetCoolerMode(0) # Returns to ambient temperature on ShutDown - # self.set_cooler_on_state(self._cooler_on) - # self.set_exposure(self._exposure) - # self.set_setpoint_temperature(self._temperature) - #self.dll = cdll.LoadLibrary(self._dll_location) - self.dll = cdll.LoadLibrary('C:/temp/atmcd64d.dll') - - self.dll.Initialize() + """ Initialization performed during activation of the module. + + """ + + self.dll = ct.cdll.LoadLibrary(self._dll_location) + self.errorcode = self._create_errorcode() + + code = self.dll.Initialize() + + if code != 20002: + self.log.info('Problem during camera (Andor/Newton) initialization') + self.on_deactivate() + else: nx_px, ny_px = c_int(), c_int() - self._get_detector(nx_px, ny_px) + nx_px, ny_py = self._get_size() self._width, self._height = nx_px.value, ny_px.value + self._set_read_mode(self._read_mode) self._set_trigger_mode(self._trigger_mode) self._set_exposuretime(self._exposure) self._set_acquisition_mode(self._acquisition_mode) def on_deactivate(self): - """ Deinitialisation performed during deactivation of the module. + """ + Deinitialisation performed during deactivation of the module. + """ self.stop_acquisition() self._set_shutter(0, 0, 0.1, 0.1) self._shut_down() +################################################################################################### +# Error management +################################################################################################### + + def check(self, func_val): + """ Check routine for the received error codes. + :return: the dll function error code + Tested : no + """ + + if not func_val == 20002: + self.log.error('Error in Shamrock with errorcode {0}:\n' + '{1}'.format(func_val, self.errorcode[func_val])) + return func_val + + def _create_errorcode(self): + """ Create a dictionary with the errorcode for the device. + """ + + maindir = get_main_dir() + + filename = os.path.join(maindir, 'hardware', 'camera', 'andor', 'errorcodes_newton.h') + try: + with open(filename) as f: + content = f.readlines() + except: + self.log.error('No file "errorcodes_newton.h" could be found in the ' + 'hardware/spectrometer directory!') + errorcode = {} + for line in content: + if '#define NEWTON940' in line: + errorstring, errorvalue = line.split()[-2:] + errorcode[int(errorvalue)] = errorstring + + return errorcode + +################################################################################################### +# Basic functions +################################################################################################### + def get_name(self): - """ Retrieve an identifier of the camera that the GUI can print + """ + :return: string local camera name with serial number + + """ + serial = ct.c_int() + self.check(self.dll.GetCameraSerialNumber(byref(serial))) + name = self._camera_name + " serial number " + str(serial.value) + return name - @return string: name for the camera + def get_image_size(self): """ - return self._camera_name + Returns the sensor size in pixels (x;y) - def get_size(self): - """ Retrieve size of the image in pixel + :return: tuple (nw_px, ny_px) : int number of pixel along x and y axis - @return tuple: Size (width, height) + Tested : no + SI check : ok """ - return self._width, self._height + nx_px = ct.c_int() + ny_px = ct.c_int() + self.check(self.dll.GetDetector(byref(nx_px), byref(ny_px))) + return nx_px.value, ny_px.value + + def get_pixel_size(self): + """ + + :return: + """ + x_px = ct.c_float() + y_py = ct.c_float() + self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) + return x_px.value*1E-6, y_py.value*1E-6 + + def get_ready_state(self): + code = ct.c_int() + self.check(self.dll.GetStatus(byref(code))) + if code.value==2073: + return True + else: + return False + + def start_acquisition(self): + """ + Starts a single acquisition + :return: nothing + Tested : no + + """ + self.check(self.dll.StartAcquisition()) + self.dll.WaitForAcquisition() + return + + def stop_acquisition(self): + """ + Stops/aborts live or single acquisition + + @return nothing + """ + self.check(self.dll.AbortAcquisition()) + return + + def set_acquisition_mode(self, acquisition_mode, **kwargs): + """ + Setter method setting the acquisition mode used by the camera. + + :param acquisition_mode: @str read mode (must be compared to a dict) + :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes + :return: nothing + """ + + + check_val = 0 + if hasattr(AcquisitionMode, mode): + n_mode = c_int(getattr(AcquisitionMode, mode).value) + error_code = self.dll.SetAcquisitionMode(n_mode) + else: + self.log.warning('{0} mode is not supported'.format(mode)) + check_val = -1 + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + check_val = -1 + else: + self._acquisition_mode = mode + + return check_val + +############################################################################## +# Read mode functions +############################################################################## + + + + + + + + + + + + + + + + + + + def support_live_acquisition(self): """ Return whether or not the camera can take care of live acquisition @@ -194,41 +338,9 @@ def start_live_acquisition(self): return False - def start_single_acquisition(self): - """ Start a single acquisition - @return bool: Success ? - """ - if self._shutter == 'closed': - msg = self._set_shutter(0, 1, 0.1, 0.1) - if msg == 'DRV_SUCCESS': - self._shutter = 'open' - else: - self.log.error('shutter did not open.{0}'.format(msg)) - if self._live: - return -1 - else: - self._acquiring = True # do we need this here? - msg = self._start_acquisition() - if msg != "DRV_SUCCESS": - return False - self._acquiring = False - return True - - def stop_acquisition(self): - """ Stop/abort live or single acquisition - - @return bool: Success ? - """ - msg = self._abort_acquisition() - if msg == "DRV_SUCCESS": - self._live = False - self._acquiring = False - return True - else: - return False def get_acquired_data(self): """ Return an array of last acquired image. @@ -589,9 +701,7 @@ def _get_status(self, status): error_code = self.dll.GetStatus(byref(status)) return ERROR_DICT[error_code] - def _get_detector(self, nx_px, ny_px): - error_code = self.dll.GetDetector(byref(nx_px), byref(ny_px)) - return ERROR_DICT[error_code] + def _get_camera_serialnumber(self, number): """ diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 1bb8d6140c..dd777f239c 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -49,6 +49,14 @@ def get_image_size(self): """ pass + @abstract_interface_method + def get_pixel_size(self): + """ Retrieve the pixel size (unit is meter) + + @return tuple: pixel_size (x,y) + """ + pass + @abstract_interface_method def start_acquisition(self): """ Start a single acquisition From d900f1267ebb5fee071b56a98b59c10472a02284 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 Apr 2020 17:18:16 +0200 Subject: [PATCH 10/49] Logic module Camera wrapper OK --- logic/spectrum_logic.py | 446 +++++++++++++++++++++++++++++++++------- 1 file changed, 374 insertions(+), 72 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 7dc0cbb197..ecdd8c7d92 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -59,18 +59,16 @@ class SpectrumLogic(GenericLogic): _number_of_track = StatusVar('number_of_track', 1) _track_height = StatusVar('track_height', 50) _track_offset = StatusVar('track_offset', 0) - _pixel_height = StatusVar('pixel_height', 1e-4) - _pixel_width = StatusVar('pixel_width', 1e-4) - _acquistion_mode = StatusVar('acquistion_mode', 'SINGLESCAN') + + _acquistion_mode = StatusVar('acquistion_mode', 'SINGLE_SCAN') + _exposure_time = StatusVar('exposure_time', 1e-4) + _camera_gain = StatusVar('camera_gain', 1) _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) - _accumulation_time = StatusVar('number_accumulated_scan', 1e-3) + _accumulation_time = StatusVar('accumulation_time', 1e-3) _number_of_scan = StatusVar('number_of_scan', 1) _scan_delay = StatusVar('scan_delay', 1e-2) - _exposure_time = StatusVar('exposure_time', 1e-4) - _camera_gain = StatusVar('camera_gain', 1) - _shutter_mode = StatusVar('shutter_mode', (1, 1e-4, 1e-4, True)) ############################################################################## # Basic functions ############################################################################## @@ -90,6 +88,7 @@ def on_activate(self): """ self.spectrometer_device = self.spectrometer() self.camera_device = self.camera() + self._save_logic = self.savelogic() # declare spectrometer attributes : self._grating = self.grating @@ -106,9 +105,13 @@ def on_activate(self): self._output_slit_width = self.output_slit_width # declare camera attributes : - self._camera_size = self.camera_device.size() + self._image_size = self.image_size + self._pixel_size = self.pixel_size + self._superpixel_size, self._superimage_size, self._superimage_position = self.image_parameters - self._save_logic = self.savelogic() + self._shutter_is_open = self.shutter_is_open + self._cooler_ON = self.cooler_ON + self._camera_temperature = self.camera_temperature def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. @@ -244,9 +247,9 @@ def grating(self, grating_number): self.log.info('Spectrometer grating has been changed correctly ') self._grating = grating_number else: - if is_int: + if not is_int: self.log.debug('Grating parameter is not correct : it must be an integer ') - if is_in_range: + if not is_in_range: self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' .format(number_gratings-1)) else: @@ -274,9 +277,9 @@ def grating_offset(self, grating_number): self.log.error('Your hardware getter function \'get_grating_offset()\' is not returning an integer ') return 0 else: - if var_is_int: + if not var_is_int: self.log.debug('Grating parameter is not correct : it must be an integer ') - if var_is_in_range: + if not var_is_in_range: self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' .format(number_gratings-1)) else: @@ -309,19 +312,19 @@ def grating_offset(self, grating_number, grating_offset): self.log.info('Spectrometer grating offset has been changed correctly ') self._grating = grating_number else: - if grating_is_int: + if not grating_is_int: self.log.debug('Grating parameter is not correct : it must be an integer ') - elif grating_is_in_range: + elif not grating_is_in_range: self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' .format(number_gratings-1)) - elif grating_is_change: + elif not grating_is_change: self.log.info('Grating parameter has not been changed') - elif offset_is_int: + elif not offset_is_int: self.log.debug('Offset parameter is not correct : it must be an integer ') - elif offset_is_in_range: + elif not offset_is_in_range: self.log.debug('Offset parameter is not correct : it must be in range {} to {} ' .format(offset_min, offset_max)) - elif offset_is_change: + elif not offset_is_change: self.log.info('Offset parameter has not been changed') ############################################################################## @@ -361,9 +364,9 @@ def center_wavelength(self, wavelength): self.log.info('Spectrometer wavelength has been changed correctly ') self._center_wavelength = wavelength else: - if is_float: + if not is_float: self.log.debug('Wavelength parameter is not correct : it must be a float ') - elif is_in_range: + elif not is_in_range: self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' .format(wavelength_min, wavelength_max)) else: @@ -385,7 +388,7 @@ def wavelength_range(self): self._wavelength_range = wavelength_range return wavelength_range else: - if is_ndarray: + if not is_ndarray: self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ndarray ') else: self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ' @@ -407,7 +410,7 @@ def number_of_pixels(self): self._pixel_matrix_dimension[0] = number_pixels return number_pixels else: - if is_int: + if not is_int: self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a int ') else: self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a ' @@ -431,9 +434,9 @@ def number_of_pixels(self, number_pixels): self.log.info('Number of pixels has been changed correctly ') self._pixel_matrix_dimension[0] = number_pixels else: - if is_int: + if not is_int: self.log.debug('Number of pixels parameter is not correct : it must be a int ') - elif is_positive: + elif not is_positive: self.log.debug('Number of pixels parameter is not correct : it must be positive ') else: self.log.info('Number of pixels parameter has not been changed') @@ -453,7 +456,7 @@ def pixel_width(self): self._pixel_width = pixel_width return pixel_width else: - if is_float: + if not is_float: self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a float ') else: self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a ' @@ -477,9 +480,9 @@ def pixel_width(self, pixel_width): self.log.info('Pixel width has been changed correctly ') self._pixel_width = pixel_width else: - if is_float: + if not is_float: self.log.debug('Pixel width parameter is not correct : it must be a float ') - elif is_positive: + elif not is_positive: self.log.debug('Pixel width parameter is not correct : it must be positive ') else: self.log.info('Pixel width parameter has not been changed') @@ -525,9 +528,9 @@ def detector_offset(self, detector_offset): self.log.info('Detector offset has been changed correctly ') self._detector_offset = detector_offset else: - if is_int: + if not is_int: self.log.debug('Detector offset parameter is not correct : it must be a int ') - elif is_in_range: + elif not is_in_range: self.log.debug('Detector offset parameter is not correct : it must be in range {} to {} ' .format(offset_min, offset_max)) else: @@ -551,7 +554,7 @@ def input_port(self): self._input_port = input_port return input_port else: - if is_int: + if not is_int: self.log.error('Your hardware getter function \'get_input_port()\' is not returning a int ') else: self.log.error('Your hardware getter function \'get_input_port()\' is not in range 0 to 1 ') @@ -577,9 +580,9 @@ def input_port(self, input_port): else: self.log.debug('Your hardware do not have any flipper mirror present at the input port ') else: - if is_int: + if not is_int: self.log.debug('Input port parameter is not correct : it must be a int ') - elif is_in_range: + elif not is_in_range: self.log.debug('Input port parameter is not correct : it must be 0 or 1 ') else: self.log.info('Input port parameter has not been changed') @@ -598,7 +601,7 @@ def output_port(self): self._input_port = output_port return output_port else: - if is_int: + if not is_int: self.log.error('Your hardware getter function \'get_output_port()\' is not returning a int ') else: self.log.error('Your hardware getter function \'get_output_port()\' is not in range 0 to 1 ') @@ -624,9 +627,9 @@ def output_port(self, output_port): else: self.log.debug('Your hardware do not have any flipper mirror present at the output port ') else: - if is_int: + if not is_int: self.log.debug('Output port parameter is not correct : it must be a int ') - elif is_in_range: + elif not is_in_range: self.log.debug('Output port parameter is not correct : it must be 0 or 1 ') else: self.log.info('Output port parameter has not been changed') @@ -682,6 +685,7 @@ def output_slit_width(self): return slit_width else: self.log.error('Your hardware getter function \'get_auto_slit_width()\' is not returning a float ') + return 0 @output_slit_width.setter def output_slit_width(self, slit_width): @@ -713,6 +717,43 @@ def output_slit_width(self, slit_width): # Basic functions ############################################################################## + @property + def image_size(self): + image_size = self.camera_device.get_image_size() + is_correct = len(image_size) == 2 + is_int = isinstance(image_size[0], int) and isinstance(image_size[1], int) + is_pos = image_size[0]>0 and image_size[1]>0 + if is_correct and is_int and is_pos: + self._image_size = image_size + return image_size + else: + if not is_correct: + self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of 2 elements ") + if not is_int: + self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of int ") + else: + self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of positive number ") + return 0 + + @property + def pixel_size(self): + pixel_size = self.camera_device.get_pixel_size() + is_correct = len(pixel_size) == 2 + is_float = isinstance(pixel_size[0], float) and isinstance(pixel_size[1], float) + is_pos = pixel_size[0]>0 and pixel_size[1]>0 + if is_correct and is_float and is_pos: + self._pixel_size = pixel_size + return pixel_size + else: + if not is_correct: + self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of 2 elements ") + if not is_float: + self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of float ") + else: + self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of positive number ") + return 0 + + @property def acquired_data(self): """ Return an array of last acquired image. @@ -740,7 +781,15 @@ def read_mode(self): :return: @str read mode (must be compared to a dict) """ - pass + read_mode = self.camera_device.get_read_mode() + is_str = isinstance(read_mode, str) + if is_str: + self._read_mode = read_mode + return read_mode + else: + self.log.error("Your hardware method 'get_read_mode()' is not returning a string ") + return 0 + @read_mode.setter def read_mode(self, read_mode): @@ -750,37 +799,107 @@ def read_mode(self, read_mode): :param read_mode: @str read mode (must be compared to a dict) :return: nothing """ - pass + is_str = isinstance(read_mode, str) + if is_str: + self.camera_device.set_read_mode(read_mode) + else: + self.log.debug("Read mode parameter must be a string ") - def get_track_parameters(self): + @property + def track_parameters(self): """ Getter method returning the read mode tracks parameters of the camera. :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error """ - pass + track_parameters = self.camera_device.get_track_parameters() + are_int = all([isinstance(track_parameter, int) for track_parameter in track_parameters]) + are_pos = all([isinstance(track_parameter, int) for track_parameter in track_parameters[:2]]) + are_in_range = len(track_parameters)==3 + number_of_track, track_height, track_offset = track_parameters + if are_int and are_pos and are_in_range: + self._number_of_track = number_of_track + self._track_height = track_height + self._track_offset = track_offset + return track_parameters + else: + if not are_int: + self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of int ") + if not are_in_range: + self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of size 3 ") + else: + self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of " + "positive numbers ") + return 0 - def set_track_parameters(self, number_of_track, track_heigth, track_offset): + @track_parameters.setter + def track_parameters(self, number_of_track, track_height, track_offset): """ Setter method setting the read mode tracks parameters of the camera. :param number_of_track: @int number of track - :param track_heigth: @int track height + :param track_height: @int track height :param track_offset: @int track offset :return: nothing """ - pass + are_int = isinstance(number_of_track, int) and isinstance(track_height, int) and isinstance(track_offset, int) + are_pos = number_of_track>0 and track_height>0 + if are_int and are_pos : + track_spacing = self._image_size[0]/(number_of_track + 2 - number_of_track%2) + number_is_correct = number_of_track<=self._image_size[0] + height_is_correct = track_height <= track_spacing + offset_is_correct = (number_of_track*track_spacing+track_height)/2 < self._image_size[0]-abs(track_offset) + if number_is_correct and height_is_correct and offset_is_correct: + self.camera_device.set_track_parameters(number_of_track, track_height, track_offset) + self._number_of_track = number_of_track + self._track_height = track_height + self._track_offset = track_offset + self.log.info("Track parameters has been set properly ") + else: + if not number_is_correct: + self.log.debug("Number of track parameter must be less than the camera number of pixels ") + if not height_is_correct: + self.log.debug("Track height parameter must be lower than track inter-spacing : " + "the tracks are covering each other ") + else: + self.log.debug("Track offset parameter must be set in the way to keep tracks inside " + "the pixel matrix : some tracks are outside the pixel area ") + else: + if not are_int: + self.log.debug("Track parameters must be integers ") + else: + self.log.debug("Number of track and track height parameters must be positive ") - def get_image_parameters(self): + @property + def image_parameters(self): """ Getter method returning the read mode image parameters of the camera. - :return: @tuple (@int pixel height, @int pixel width, @tuple (@int start raw, @int end raw), + :return: @tuple (@int superpixel height, @int superpixel width, @tuple (@int start raw, @int star column), @tuple (@int start column, @int end column)) or 0 if error """ - pass + image_parameters = self.camera_device.get_image_parameters() + are_int = all([isinstance(image_parameter, int) for image_parameter in image_parameters]) + are_pos = all([image_parameter > 0 for image_parameter in image_parameters]) + are_in_range = len(image_parameters) == 3 + superpixel_size, superimage_size, superimage_position = image_parameters + if are_int and are_pos and are_in_range: + self._superpixel_size = superpixel_size + self._superimage_size = superimage_size + self._superimage_position = superimage_position + return image_parameters + else: + if not are_int: + self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of int ") + if not are_in_range: + self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of size 3 ") + else: + self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of " + "positive numbers ") + return 0 - def set_image_parameters(self, pixel_height, pixel_width, raw_range, column_range): + @image_parameters.setter + def image_parameters(self, superpixel_size, superimage_size, superimage_position): """ Setter method setting the read mode image parameters of the camera. @@ -790,7 +909,26 @@ def set_image_parameters(self, pixel_height, pixel_width, raw_range, column_rang :param column_range: @tuple (@int start column, @int end column) :return: nothing """ - pass + image_parameters = ( superpixel_size, superimage_size, superimage_position) + are_int = all([isinstance(image_parameter, int) for image_parameter in image_parameters]) + are_pos = all([image_parameter > 0 for image_parameter in image_parameters]) + if are_int and are_pos: + superraw_is_correct = superpixel_size[0]*superimage_size[0]0 + is_in_range = self._exposure_time < accumulation_time < self._scan_delay + if is_float and is_pos and is_in_range: + self._accumulation_time = accumulation_time + return accumulation_time + else: + if not is_float: + self.log.error("Your hardware method 'get_accumulation_time()' is not returning a float ") + if not is_pos: + self.log.error("Your hardware method 'get_accumulation_time()' is not returning a positive number ") + else: + self.log.error("Your hardware method 'get_accumulation_time()' is not returning a value between" + "the current exposure time and scan delay values ") + return 0 @accumulation_time.setter def accumulation_time(self, accumulation_time): @@ -835,7 +1000,20 @@ def accumulation_time(self, accumulation_time): :param accumulation_time: @int accumulation cycle time :return: nothing """ - pass + is_float = isinstance(accumulation_time, float) + is_pos = accumulation_time > 0 + is_in_range = self._exposure_time < accumulation_time < self._scan_delay + if is_float and is_pos and is_in_range: + self._accumulation_time = accumulation_time + self.camera_device.set_accumulation_time(accumulation_time) + else: + if not is_float: + self.log.debug("Accumulation time parameter must be a float ") + if not is_pos: + self.log.debug("Accumulation time parameter must be a positive number ") + else: + self.log.debug("Accumulation time parameter must be a value between" + "the current exposure time and scan delay values ") @property def number_accumulated_scan(self): @@ -845,7 +1023,18 @@ def number_accumulated_scan(self): :return: @int number of accumulated scan or 0 if error """ - pass + number_scan = self.camera_device.get_number_accumulated_scan() + is_int = isinstance(number_scan, int) + is_pos = number_scan > 0 + if is_int and is_pos: + self._number_accumulated_scan = number_scan + return number_scan + else: + if not is_int: + self.log.error("Your hardware method 'get_number_accumulated_scan()' is not returning a int ") + else: + self.log.error("Your hardware method 'get_number_accumulated_scan()' is not returning a positive number ") + return 0 @number_accumulated_scan.setter def number_accumulated_scan(self, number_scan): @@ -856,7 +1045,16 @@ def number_accumulated_scan(self, number_scan): :param number_scan: @int number of accumulated scan :return: nothing """ - pass + is_int = isinstance(number_scan, int) + is_pos = number_scan > 0 + if is_int and is_pos: + self._number_accumulated_scan = number_scan + self.camera_device.set_number_accumulated_scan(number_scan) + else: + if not is_int: + self.log.debug("Number of accumulated scan parameter must be an integer ") + else: + self.log.debug("Number of accumulated scan parameter must be positive ") @property def exposure_time(self): @@ -864,7 +1062,22 @@ def exposure_time(self): @return float exposure time """ - pass + exposure_time = self.camera_device.get_exposure_time() + is_float = isinstance(exposure_time, float) + is_pos = exposure_time > 0 + is_in_range = exposure_time < self._accumulation_time + if is_float and is_pos and is_in_range: + self._exposure_time = exposure_time + return exposure_time + else: + if not is_float: + self.log.error("Your hardware method 'get_exposure_time()' is not returning a float ") + if not is_pos: + self.log.error("Your hardware method 'get_exposure_time()' is not returning a positive number ") + else: + self.log.error("Your hardware method 'get_exposure_time()' is not returning a value lower" + "that the current accumulation time value ") + return 0 @exposure_time.setter def exposure_time(self, exposure_time): @@ -874,7 +1087,20 @@ def exposure_time(self, exposure_time): @return float: setted new exposure time """ - pass + is_float = isinstance(exposure_time, float) + is_pos = exposure_time > 0 + is_in_range = exposure_time < self._accumulation_time + if is_float and is_pos and is_in_range: + self._exposure_time = exposure_time + self.camera_device.set_exposure_time(exposure_time) + else: + if not is_float: + self.log.debug("Exposure time parameter must be a float ") + if not is_pos: + self.log.debug("Exposure time parameter must be a positive number ") + else: + self.log.debug("Exposure time parameter must be a value lower" + "that the current accumulation time values ") @property def camera_gain(self): @@ -882,17 +1108,37 @@ def camera_gain(self): @return float: exposure gain """ - pass + camera_gain = self.camera_device.get_camera_gain() + is_float = isinstance(camera_gain, float) + is_pos = camera_gain > 0 + if is_float and is_pos : + self._camera_gain = camera_gain + return camera_gain + else: + if not is_float: + self.log.error("Your hardware method 'get_camera_gain()' is not returning a float ") + else: + self.log.error("Your hardware method 'get_camera_gain()' is not returning a positive number ") + return 0 @camera_gain.setter - def camera_gain(self, gain): + def camera_gain(self, camera_gain): """ Set the gain @param float gain: desired new gain @return float: new exposure gain """ - pass + is_float = isinstance(camera_gain, float) + is_pos = camera_gain > 0 + if is_float and is_pos: + self._camera_gain = camera_gain + self.camera_device.set_camera_gain(camera_gain) + else: + if not is_float: + self.log.debug("Camera gain parameter must be a float ") + else: + self.log.debug("Camera gain parameter must be a positive number ") ############################################################################## # Trigger mode functions @@ -905,7 +1151,14 @@ def trigger_mode(self): :return: @str trigger mode (must be compared to a dict) """ - pass + trigger_mode = self.camera_device.get_trigger_mode() + is_str = isinstance(trigger_mode, str) + if is_str: + self._trigger_mode = trigger_mode + return trigger_mode + else: + self.log.error("Your hardware method 'get_trigger_mode()' is not returning a string ") + return 0 @trigger_mode.setter def trigger_mode(self, trigger_mode): @@ -915,7 +1168,12 @@ def trigger_mode(self, trigger_mode): :param trigger_mode: @str trigger mode (must be compared to a dict) :return: nothing """ - pass + is_str = isinstance(trigger_mode, str) + if is_str: + self._trigger_mode = trigger_mode + self.camera_device.set_trigger_mode(trigger_mode) + else: + self.log.debug("Trigger mode parameter must be a string ") ############################################################################## # Shutter mode functions @@ -926,9 +1184,16 @@ def shutter_is_open(self): """ Getter method returning if the shutter is open. - :return: @bool shutter open ? + :return: @bool shutter open ? or 0 if error """ - pass + shutter_open = self.camera_device.get_shutter_is_open() + is_bool = isinstance(shutter_open, bool) + if is_bool: + self._shutter_is_open = shutter_open + return shutter_open + else: + self.log.error("Your hardware method 'get_shutter_is_open()' is not returning a boolean ") + return 0 @shutter_is_open.setter def shutter_is_open(self, shutter_open): @@ -938,7 +1203,12 @@ def shutter_is_open(self, shutter_open): :param shutter_mode: @bool shutter open :return: nothing """ - pass + is_bool = isinstance(shutter_open, str) + if is_bool: + self._shutter_is_open = shutter_open + self.camera_device.set_shutter_is_open(shutter_open) + else: + self.log.debug("Shutter open mode parameter must be a boolean ") ############################################################################## # Temperature functions @@ -951,7 +1221,14 @@ def cooler_ON(self): :return: @bool True if ON or False if OFF or 0 if error """ - pass + cooler_ON = self.camera_device.get_cooler_ON() + is_bool = isinstance(cooler_ON, bool) + if is_bool: + self._cooler_ON = cooler_ON + return cooler_ON + else: + self.log.error("Your hardware method 'get_cooler_ON()' is not returning a boolean ") + return 0 @cooler_ON.setter def cooler_ON(self, cooler_ON): @@ -961,23 +1238,48 @@ def cooler_ON(self, cooler_ON): :cooler_ON: @bool True if ON or False if OFF :return: nothing """ - pass + is_bool = isinstance(cooler_ON, str) + if is_bool: + self._cooler_ON = cooler_ON + self.camera_device.set_cooler_ON(cooler_ON) + else: + self.log.debug("Cooler status parameter must be a boolean ") @property - def temperature(self): + def camera_temperature(self): """ Getter method returning the temperature of the camera. :return: @float temperature or 0 if error """ - pass + camera_temperature = self.camera_device.get_temperature() + is_float = isinstance(camera_temperature, float) + is_pos = camera_temperature > 0 + if is_float and is_pos: + self._camera_temperature = camera_temperature + return camera_temperature + else: + if not is_float: + self.log.error("Your hardware method 'get_temperature()' is not returning a float ") + else: + self.log.error("Your hardware method 'get_temperature()' is not returning a positive number ") + return 0 - @temperature.setter - def temperature(self, temperature): + @camera_temperature.setter + def camera_temperature(self, camera_temperature): """ Getter method returning the temperature of the camera. :param temperature: @float temperature or 0 if error :return: nothing """ - pass \ No newline at end of file + is_float = isinstance(camera_temperature, float) + is_pos = camera_temperature > 0 + if is_float and is_pos: + self._camera_temperature = camera_temperature + self.camera_device.set_temperature(camera_temperature) + else: + if not is_float: + self.log.debug("Camera temperature parameter must be a float ") + else: + self.log.debug("Camera temperature parameter must be a positive number ") \ No newline at end of file From 6e039fc71806f9e6f67df356bae7af8180c121bb Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 Apr 2020 17:39:04 +0200 Subject: [PATCH 11/49] Complete camera interface update 4 --- interface/camera_complete_interface.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index dd777f239c..ffb567678c 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -147,14 +147,13 @@ def get_image_parameters(self): pass @abstract_interface_method - def set_image_parameters(self, pixel_height, pixel_width, raw_range, column_range): + def set_image_parameters(self, superpixel_size, superimage_size, superimage_position): """ Setter method setting the read mode image parameters of the camera. - :param pixel_height: @int pixel height - :param pixel_width: @int pixel width - :param raw_range: @tuple (@int start raw, @int end raw) - :param column_range: @tuple (@int start column, @int end column) + :param superpixel_size: @tuple (@int start raw, @int end raw) + :param superimage_size: @tuple (@int number of raw, @int number of column) + :param superimage_position: @tuple (@int bottom left corner raw, @int bottom left corner column) :return: nothing """ pass @@ -312,7 +311,7 @@ def shutter_is_open(self, shutter_open): ############################################################################## @abstract_interface_method - def cooler_ON(self): + def get_cooler_ON(self): """ Getter method returning the cooler status if ON or OFF. @@ -321,7 +320,7 @@ def cooler_ON(self): pass @abstract_interface_method - def cooler_ON(self, cooler_ON): + def get_cooler_ON(self, cooler_ON): """ Getter method returning the cooler status if ON or OFF. From 1aaf62ec5230fc62ce8f1e6ac5aaf1eb2884ed8c Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 10 Apr 2020 20:00:28 +0200 Subject: [PATCH 12/49] Logic version 1 (ready for review) Please take a look on our first logic complete version --- logic/spectrum_logic.py | 129 +++++++++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 35 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index ecdd8c7d92..f393072887 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -109,7 +109,7 @@ def on_activate(self): self._pixel_size = self.pixel_size self._superpixel_size, self._superimage_size, self._superimage_position = self.image_parameters - self._shutter_is_open = self.shutter_is_open + self._shutter_mode = self.shutter_mode self._cooler_ON = self.cooler_ON self._camera_temperature = self.camera_temperature @@ -162,34 +162,55 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): ############################################################################## def acquire_spectrum(self): - for i in range(0, self._number_of_scan): - self.camera_device.start_acquisition() - self._spectrum_data[i] = self.camera_device.get_acquired_data() - sleep(self._scan_delay) - self.log.info("Spectrum acquisition succeed ! Number of acquired scan : {} " - "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + if self._acquistion_mode == 'LIVE_ACQUISITION': + self.module_state = 'running' + while self.module_state == 'running': + self.shutter_mode(1) + self.camera_device.start_acquisition() + self.shutter_mode(0) + self._spectrum_data = self.acquired_data() + sleep(self._scan_delay) + self.module_state = 'idle' + else: + scans = np.empty(self._number_of_scan) + self.module_state = 'running' + for i in range(0, self._number_of_scan): + self.shutter_mode(1) + self.camera_device.start_acquisition() + self.shutter_mode(0) + scans[i] = self.acquired_data() + sleep(self._scan_delay) + self.module_state = 'idle' + self._spectrum_data = scans + self.log.info("Spectrum acquisition succeed ! Number of acquired scan : {} " + "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) def acquire_background(self): - if self.get_shutter_mode()[0] != 0: - self.set_shutter_mode(0) + scans = np.empty(self._number_of_scan) + self.shutter_mode(0) # Force the shutter to be close during background acquisition process + self.module_state = 'running' for i in range(0, self._number_of_scan): self.camera_device.start_acquisition() - self._background_data[i] = self.camera_device.get_acquired_data() + scans[i] = self.acquired_data() sleep(self._scan_delay) + self.module_state = 'idle' + self._background_data = scans self.log.info("Background acquisition succeed ! Number of acquired scan : {} " "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) - self.set_shutter_mode(*self._shutter_mode) def acquire_image(self): - if self.get_shutter_mode()[0] != 0: - self.set_shutter_mode(0) + scans = np.empty(self._number_of_scan) + self.module_state = 'running' for i in range(0, self._number_of_scan): + self.shutter_mode(1) self.camera_device.start_acquisition() - self._background_data[i] = self.camera_device.get_acquired_data() + self.shutter_mode(0) + scans[i] = self.acquired_data() sleep(self._scan_delay) - self.log.info("Background acquisition succeed ! Number of acquired scan : {} " + self.module_state = 'idle' + self._image_data = scans + self.log.info("Image acquisition succeed ! Number of acquired scan : {} " "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) - self.set_shutter_mode(*self._shutter_mode) @property def spectrum_data(self): @@ -453,7 +474,6 @@ def pixel_width(self): is_float = isinstance(pixel_width, float) is_positive = 0 < pixel_width if is_float and is_positive: - self._pixel_width = pixel_width return pixel_width else: if not is_float: @@ -474,11 +494,10 @@ def pixel_width(self, pixel_width): """ is_float = isinstance(pixel_width, float) is_positive = 0 < pixel_width - is_change = pixel_width != self._pixel_width + is_change = pixel_width != self._pixel_size[0] if is_float and is_positive and is_change: self.spectrometer_device.set_pixel_width(pixel_width) self.log.info('Pixel width has been changed correctly ') - self._pixel_width = pixel_width else: if not is_float: self.log.debug('Pixel width parameter is not correct : it must be a float ') @@ -517,7 +536,7 @@ def detector_offset(self, detector_offset): :param detector_offset: @int detetcor offset :return: nothing """ - number_pixels = 514 #TODO : add the Newton funtion returning the number of pixels (Hardcoding) + number_pixels = self._image_size[0] offset_min = -number_pixels//2 - 1 offset_max = number_pixels//2 is_int = isinstance(detector_offset, int) @@ -717,6 +736,14 @@ def output_slit_width(self, slit_width): # Basic functions ############################################################################## + def start_acquisition(self): + self.camera_device.start_acquisition() + + def stop_acquisition(self): + self.camera_device.stop_acquisition() + self.module_state = 'idle' + self.log.info("Stop acquisition : module state is 'idle' ") + @property def image_size(self): image_size = self.camera_device.get_image_size() @@ -761,6 +788,38 @@ def acquired_data(self): @return numpy array: image data in format [[row],[row]...] Each pixel might be a float, integer or sub pixels """ + data = self.camera_device.get_acquired_data() + data = np.array(data) # Data are forced to be a ndarray + is_ndarray = isinstance(data, np.ndarray) + is_float = isinstance(data.dtype, float) + if is_ndarray and is_float: + if self._read_mode == 'IMAGE': + is_correct_dim = np.shape(data)[0, 2] == self._superimage_size + if not is_correct_dim: + data = data[::-1] + is_correct_dim = np.shape(data)[0, 2] == self._superimage_size + elif self._read_mode == 'MULTI_TRACK': + is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) + if not is_correct_dim: + data = data[::-1] + is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) + else: + is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) + if not is_correct_dim: + data = data[::-1] + is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) + if is_correct_dim: + return data + else: + self.log.error("Your hardware function 'get_acquired_data()'" + " is not returning a numpy array with the expected dimension ") + if not is_float : + self.log.debug("Your hardware function 'get_acquired_data()'" + " is not returning a numpy array with dtype float ") + else: + self.log.debug("Your hardware function 'get_acquired_data()' is not returning a numpy array ") + return np.empty((2, 0)) + @property def ready_state(self): @@ -1180,35 +1239,35 @@ def trigger_mode(self, trigger_mode): ############################################################################## @property - def shutter_is_open(self): + def shutter_mode(self): """ Getter method returning if the shutter is open. - :return: @bool shutter open ? or 0 if error + :return: @int shutter mode (0 permanently closed, 1 permanently open, 2 mode auto) or 4 if error """ shutter_open = self.camera_device.get_shutter_is_open() - is_bool = isinstance(shutter_open, bool) - if is_bool: - self._shutter_is_open = shutter_open + is_int = isinstance(shutter_open, int) + if is_int: + self._shutter_mode = shutter_open return shutter_open else: - self.log.error("Your hardware method 'get_shutter_is_open()' is not returning a boolean ") - return 0 + self.log.error("Your hardware method 'get_shutter_is_open()' is not returning an int ") + return 4 - @shutter_is_open.setter - def shutter_is_open(self, shutter_open): + @shutter_mode.setter + def shutter_mode(self, shutter_mode): """ Setter method setting if the shutter is open. - :param shutter_mode: @bool shutter open + :param shutter_mode: @int shutter mode (0 permanently closed, 1 permanently open, 2 mode auto) :return: nothing """ - is_bool = isinstance(shutter_open, str) - if is_bool: - self._shutter_is_open = shutter_open - self.camera_device.set_shutter_is_open(shutter_open) + is_int = isinstance(shutter_mode, int) + if is_int: + self._shutter_mode = shutter_mode + self.camera_device.set_shutter_is_open(shutter_mode) else: - self.log.debug("Shutter open mode parameter must be a boolean ") + self.log.debug("Shutter open mode parameter must be an int ") ############################################################################## # Temperature functions From 009627bec15bf0de4d1d194d2483c5d57e2adad8 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 12 Apr 2020 00:54:51 +0200 Subject: [PATCH 13/49] Complete interface update 5 --- interface/camera_complete_interface.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index ffb567678c..23e2925658 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -183,22 +183,22 @@ def set_acquisition_mode(self, acquisition_mode): pass @abstract_interface_method - def get_accumulation_time(self): + def get_accumulation_delay(self): """ - Getter method returning the accumulation cycle time scan carry out during an accumulate acquisition mode + Getter method returning the accumulation cycle delay scan carry out during an accumulate acquisition mode by the camera. - :return: @int accumulation cycle time or 0 if error + :return: @int accumulation cycle delay or 0 if error """ pass @abstract_interface_method - def set_accumulation_time(self, accumulation_time): + def set_accumulation_delay(self, accumulation_delay): """ - Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode + Setter method setting the accumulation cycle delay scan carry out during an accumulate acquisition mode by the camera. - :param accumulation_time: @int accumulation cycle time + :param accumulation_time: @int accumulation cycle delay :return: nothing """ pass From 725fb920960864cca5b77726d460160b33468479 Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 14 Apr 2020 12:29:42 +0200 Subject: [PATCH 14/49] Logic module update 1 I don't remembre what I did ! --- logic/spectrum_logic.py | 229 +++++++++++++++++++++++++++------------- 1 file changed, 156 insertions(+), 73 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index f393072887..a0cf57aab2 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -60,14 +60,15 @@ class SpectrumLogic(GenericLogic): _track_height = StatusVar('track_height', 50) _track_offset = StatusVar('track_offset', 0) - _acquistion_mode = StatusVar('acquistion_mode', 'SINGLE_SCAN') _exposure_time = StatusVar('exposure_time', 1e-4) _camera_gain = StatusVar('camera_gain', 1) - _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) - _accumulation_time = StatusVar('accumulation_time', 1e-3) _number_of_scan = StatusVar('number_of_scan', 1) _scan_delay = StatusVar('scan_delay', 1e-2) + _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) + _accumulation_delay = StatusVar('accumulation_delay', 1e-3) + + _trigger_mode = StatusVar('trigger_mode', 'INTERNAL') ############################################################################## # Basic functions @@ -95,9 +96,10 @@ def on_activate(self): self._grating_offset = self.grating_offset self._center_wavelength = self.center_wavelength + self._wavelength_limit = self.wavelength_limit self._wavelength_range = self.wavelength_range - self._detector_offset = self.detector_offset + self.detector_offset = self._track_offset self._input_port = self.input_port self._output_port = self.output_port @@ -161,26 +163,45 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): # Acquisition functions ############################################################################## + def start_acquisition(self): + """ + Start acquisition method calling the start acquisition method from the hardware module and changing the + logic module state to 'locked'. + + :return: nothing + """ + self.camera_device.start_acquisition() + self.module_state.lock() + + def stop_acquisition(self): + """ + Stop acquisition method calling the stop acquisition method from the hardware module and changing the + logic module state to 'unlocked'. + + :return: nothing + """ + self.camera_device.stop_acquisition() + self.module_state.unlock() + self.log.info("Stop acquisition : module state is 'idle' ") + def acquire_spectrum(self): + self.module_state.lock() if self._acquistion_mode == 'LIVE_ACQUISITION': - self.module_state = 'running' - while self.module_state == 'running': + while self.module_state() == 'locked': self.shutter_mode(1) self.camera_device.start_acquisition() self.shutter_mode(0) self._spectrum_data = self.acquired_data() sleep(self._scan_delay) - self.module_state = 'idle' else: scans = np.empty(self._number_of_scan) - self.module_state = 'running' for i in range(0, self._number_of_scan): self.shutter_mode(1) self.camera_device.start_acquisition() self.shutter_mode(0) scans[i] = self.acquired_data() sleep(self._scan_delay) - self.module_state = 'idle' + self.module_state.unlock() self._spectrum_data = scans self.log.info("Spectrum acquisition succeed ! Number of acquired scan : {} " "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) @@ -188,26 +209,26 @@ def acquire_spectrum(self): def acquire_background(self): scans = np.empty(self._number_of_scan) self.shutter_mode(0) # Force the shutter to be close during background acquisition process - self.module_state = 'running' + self.module_state.lock() for i in range(0, self._number_of_scan): self.camera_device.start_acquisition() scans[i] = self.acquired_data() sleep(self._scan_delay) - self.module_state = 'idle' + self.module_state.unlock() self._background_data = scans self.log.info("Background acquisition succeed ! Number of acquired scan : {} " "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) def acquire_image(self): scans = np.empty(self._number_of_scan) - self.module_state = 'running' + self.module_state.lock() for i in range(0, self._number_of_scan): self.shutter_mode(1) self.camera_device.start_acquisition() self.shutter_mode(0) scans[i] = self.acquired_data() sleep(self._scan_delay) - self.module_state = 'idle' + self.module_state.unlock() self._image_data = scans self.log.info("Image acquisition succeed ! Number of acquired scan : {} " "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) @@ -393,6 +414,22 @@ def center_wavelength(self, wavelength): else: self.log.info('Wavelength parameter has not been changed') + @property + def wavelength_limit(self): + wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) + is_float = isinstance(wavelength_min, float) and isinstance(wavelength_max, float) + is_sort = 0 < wavelength_min < wavelength_max + if is_float and is_sort: + self._wavelength_limit = (wavelength_min, wavelength_max) + return (wavelength_min, wavelength_max) + else: + if not is_float: + self.log.error("Your hardware getter function 'get_wavelength_limit()' is not returning a " + "float type tuple ") + if is_sort: + self.log.error("Your hardware getter function 'get_wavelength_limit()' is not returning a " + "sorted values or negative values ") + @property def wavelength_range(self): """ @@ -521,7 +558,7 @@ def detector_offset(self): offset = self.spectrometer_device.get_detector_offset() is_int = isinstance(offset, int) if is_int: - self._detector_offset = offset + self._track_offset = offset return offset else: self.log.error('Your hardware getter function \'get_detector_offset()\' is not returning a int ') @@ -541,11 +578,11 @@ def detector_offset(self, detector_offset): offset_max = number_pixels//2 is_int = isinstance(detector_offset, int) is_in_range = offset_min - 1 < detector_offset < offset_max + 1 - is_change = detector_offset != self._detector_offset + is_change = detector_offset != self._track_offset if is_int and is_in_range and is_change: self.spectrometer_device.set_detector_offset(detector_offset) self.log.info('Detector offset has been changed correctly ') - self._detector_offset = detector_offset + self._track_offset = detector_offset else: if not is_int: self.log.debug('Detector offset parameter is not correct : it must be a int ') @@ -679,7 +716,8 @@ def input_slit_width(self, slit_width): """ slit_is_present = self.spectrometer_device.auto_slit_is_present('input', self._input_port) is_float = isinstance(slit_width, float) - if is_float: + is_change = slit_width == self._input_slit_width + if is_float and is_change: if slit_is_present: self.spectrometer_device.set_auto_slit_width('input', self._input_port, slit_width) self.log.info('Output slit width has been changed correctly ') @@ -687,7 +725,10 @@ def input_slit_width(self, slit_width): else: self.log.debug('Your hardware do not have any auto slit present at the selected input port ') else: - self.log.debug('Input slit width parameter is not correct : it must be a float ') + if not is_float: + self.log.debug('Input slit width parameter is not correct : it must be a float ') + else: + self.log.info("Input slit width parameter has not be changed ") @property @@ -716,7 +757,8 @@ def output_slit_width(self, slit_width): """ slit_is_present = self.spectrometer_device.auto_slit_is_present('output', self._output_port) is_float = isinstance(slit_width, float) - if is_float: + is_change = slit_width == self._output_slit_width + if is_float and is_change: if slit_is_present: self.spectrometer_device.set_auto_slit_width('output', self._output_port, slit_width) self.log.info('Output slit width has been changed correctly ') @@ -724,7 +766,11 @@ def output_slit_width(self, slit_width): else: self.log.debug('Your hardware do not have any auto slit present at the selected output port ') else: - self.log.debug('Output slit width parameter is not correct : it must be a float ') + if not is_float: + self.log.debug('Output slit width parameter is not correct : it must be a float ') + else: + self.log.info("Output slit width parameter has not be changed ") + ############################################################################## # Camera functions @@ -736,14 +782,6 @@ def output_slit_width(self, slit_width): # Basic functions ############################################################################## - def start_acquisition(self): - self.camera_device.start_acquisition() - - def stop_acquisition(self): - self.camera_device.stop_acquisition() - self.module_state = 'idle' - self.log.info("Stop acquisition : module state is 'idle' ") - @property def image_size(self): image_size = self.camera_device.get_image_size() @@ -859,10 +897,14 @@ def read_mode(self, read_mode): :return: nothing """ is_str = isinstance(read_mode, str) - if is_str: + is_change = read_mode == self._read_mode + if is_str and is_change: self.camera_device.set_read_mode(read_mode) else: - self.log.debug("Read mode parameter must be a string ") + if not is_str: + self.log.debug("Read mode parameter must be a string ") + else: + self.log.info("Read mode parameter has not be changed ") @property def track_parameters(self): @@ -903,9 +945,12 @@ def track_parameters(self, number_of_track, track_height, track_offset): """ are_int = isinstance(number_of_track, int) and isinstance(track_height, int) and isinstance(track_offset, int) are_pos = number_of_track>0 and track_height>0 - if are_int and are_pos : + number_is_change = number_of_track == self._number_of_track + height_is_change = track_height == self._track_height + offset_is_change = track_offset == self._track_offset + if are_int and are_pos and (number_is_change or height_is_change or offset_is_change): track_spacing = self._image_size[0]/(number_of_track + 2 - number_of_track%2) - number_is_correct = number_of_track<=self._image_size[0] + number_is_correct = number_of_track <= self._image_size[0] height_is_correct = track_height <= track_spacing offset_is_correct = (number_of_track*track_spacing+track_height)/2 < self._image_size[0]-abs(track_offset) if number_is_correct and height_is_correct and offset_is_correct: @@ -926,8 +971,10 @@ def track_parameters(self, number_of_track, track_height, track_offset): else: if not are_int: self.log.debug("Track parameters must be integers ") - else: + if not are_pos: self.log.debug("Number of track and track height parameters must be positive ") + else: + self.log.info("Track parameters has not be changed ") @property def image_parameters(self): @@ -971,7 +1018,10 @@ def image_parameters(self, superpixel_size, superimage_size, superimage_position image_parameters = ( superpixel_size, superimage_size, superimage_position) are_int = all([isinstance(image_parameter, int) for image_parameter in image_parameters]) are_pos = all([image_parameter > 0 for image_parameter in image_parameters]) - if are_int and are_pos: + pixel_is_change = superpixel_size == self._superpixel_size + image_is_change = superimage_size == self._superimage_size + image_pos_is_change = superimage_position == self._superimage_position + if are_int and are_pos and (pixel_is_change or image_is_change or image_pos_is_change): superraw_is_correct = superpixel_size[0]*superimage_size[0]0 - is_in_range = self._exposure_time < accumulation_time < self._scan_delay + accumulation_delay = self.camera_device.get_accumulation_delay() + is_float = isinstance(accumulation_delay, float) + is_pos = accumulation_delay>0 + is_in_range = self._exposure_time < accumulation_delay < self._scan_delay if is_float and is_pos and is_in_range: - self._accumulation_time = accumulation_time - return accumulation_time + self._accumulation_delay = accumulation_delay + return accumulation_delay else: if not is_float: - self.log.error("Your hardware method 'get_accumulation_time()' is not returning a float ") + self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a float ") if not is_pos: - self.log.error("Your hardware method 'get_accumulation_time()' is not returning a positive number ") + self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a positive number ") else: - self.log.error("Your hardware method 'get_accumulation_time()' is not returning a value between" + self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a value between" "the current exposure time and scan delay values ") return 0 - @accumulation_time.setter - def accumulation_time(self, accumulation_time): + @accumulation_delay.setter + def accumulation_delay(self, accumulation_delay): """ Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode by the camera. - :param accumulation_time: @int accumulation cycle time + :param accumulation_delay: @int accumulation cycle time :return: nothing """ - is_float = isinstance(accumulation_time, float) - is_pos = accumulation_time > 0 - is_in_range = self._exposure_time < accumulation_time < self._scan_delay - if is_float and is_pos and is_in_range: - self._accumulation_time = accumulation_time - self.camera_device.set_accumulation_time(accumulation_time) + is_float = isinstance(accumulation_delay, float) + is_pos = accumulation_delay > 0 + is_in_range = self._exposure_time < accumulation_delay < self._scan_delay + is_change = accumulation_delay == self._accumulation_delay + if is_float and is_pos and is_in_range and is_change: + self._accumulation_delay = accumulation_delay + self.camera_device.set_accumulation_delay(accumulation_delay) else: if not is_float: self.log.debug("Accumulation time parameter must be a float ") if not is_pos: self.log.debug("Accumulation time parameter must be a positive number ") - else: + if not is_in_range: self.log.debug("Accumulation time parameter must be a value between" "the current exposure time and scan delay values ") + else: + self.log.info("Accumulation time parameter has not be changed ") @property def number_accumulated_scan(self): @@ -1106,14 +1165,17 @@ def number_accumulated_scan(self, number_scan): """ is_int = isinstance(number_scan, int) is_pos = number_scan > 0 - if is_int and is_pos: + is_change = number_scan == self._number_of_scan + if is_int and is_pos and is_change: self._number_accumulated_scan = number_scan self.camera_device.set_number_accumulated_scan(number_scan) else: if not is_int: self.log.debug("Number of accumulated scan parameter must be an integer ") - else: + if not is_pos: self.log.debug("Number of accumulated scan parameter must be positive ") + else: + self.log.info("Number of scan parameter has not be changed ") @property def exposure_time(self): @@ -1124,7 +1186,7 @@ def exposure_time(self): exposure_time = self.camera_device.get_exposure_time() is_float = isinstance(exposure_time, float) is_pos = exposure_time > 0 - is_in_range = exposure_time < self._accumulation_time + is_in_range = exposure_time < self._accumulation_delay if is_float and is_pos and is_in_range: self._exposure_time = exposure_time return exposure_time @@ -1148,8 +1210,9 @@ def exposure_time(self, exposure_time): """ is_float = isinstance(exposure_time, float) is_pos = exposure_time > 0 - is_in_range = exposure_time < self._accumulation_time - if is_float and is_pos and is_in_range: + is_in_range = exposure_time < self._accumulation_delay + is_change = exposure_time == self._exposure_time + if is_float and is_pos and is_in_range and is_change: self._exposure_time = exposure_time self.camera_device.set_exposure_time(exposure_time) else: @@ -1157,9 +1220,11 @@ def exposure_time(self, exposure_time): self.log.debug("Exposure time parameter must be a float ") if not is_pos: self.log.debug("Exposure time parameter must be a positive number ") - else: + if not is_in_range: self.log.debug("Exposure time parameter must be a value lower" "that the current accumulation time values ") + else: + self.log.info("Exposure time parameter has not be changed ") @property def camera_gain(self): @@ -1190,14 +1255,17 @@ def camera_gain(self, camera_gain): """ is_float = isinstance(camera_gain, float) is_pos = camera_gain > 0 - if is_float and is_pos: + is_change = camera_gain == self._camera_gain + if is_float and is_pos and is_change: self._camera_gain = camera_gain self.camera_device.set_camera_gain(camera_gain) else: if not is_float: self.log.debug("Camera gain parameter must be a float ") - else: + if not is_pos: self.log.debug("Camera gain parameter must be a positive number ") + else: + self.log.info("Camera gain parameter has not be changed ") ############################################################################## # Trigger mode functions @@ -1228,11 +1296,15 @@ def trigger_mode(self, trigger_mode): :return: nothing """ is_str = isinstance(trigger_mode, str) - if is_str: + is_change = trigger_mode == self._trigger_mode + if is_str and is_change: self._trigger_mode = trigger_mode self.camera_device.set_trigger_mode(trigger_mode) else: - self.log.debug("Trigger mode parameter must be a string ") + if not is_str: + self.log.debug("Trigger mode parameter must be a string ") + else: + self.log.info("Trigger mode parameter has not be changed ") ############################################################################## # Shutter mode functions @@ -1263,11 +1335,15 @@ def shutter_mode(self, shutter_mode): :return: nothing """ is_int = isinstance(shutter_mode, int) - if is_int: + is_change = shutter_mode == self._shutter_mode + if is_int and is_change: self._shutter_mode = shutter_mode self.camera_device.set_shutter_is_open(shutter_mode) else: - self.log.debug("Shutter open mode parameter must be an int ") + if not is_int: + self.log.debug("Shutter open mode parameter must be an int ") + else: + self.log.info("Shutter mode parameter has not be changed ") ############################################################################## # Temperature functions @@ -1298,11 +1374,15 @@ def cooler_ON(self, cooler_ON): :return: nothing """ is_bool = isinstance(cooler_ON, str) - if is_bool: + is_change = cooler_ON == self._cooler_ON + if is_bool and is_change: self._cooler_ON = cooler_ON self.camera_device.set_cooler_ON(cooler_ON) else: - self.log.debug("Cooler status parameter must be a boolean ") + if not is_bool: + self.log.debug("Cooler status parameter must be a boolean ") + else: + self.log.info("Cooler status parameter has not be changed ") @property def camera_temperature(self): @@ -1334,11 +1414,14 @@ def camera_temperature(self, camera_temperature): """ is_float = isinstance(camera_temperature, float) is_pos = camera_temperature > 0 - if is_float and is_pos: + is_change = camera_temperature == self._camera_temperature + if is_float and is_pos and is_change: self._camera_temperature = camera_temperature self.camera_device.set_temperature(camera_temperature) else: if not is_float: self.log.debug("Camera temperature parameter must be a float ") + if not is_pos: + self.log.debug("Camera temperature parameter must be a positive number ") else: - self.log.debug("Camera temperature parameter must be a positive number ") \ No newline at end of file + self.log.info("Camera temperature parameter has not be changed ") \ No newline at end of file From 282b27680b4342028b24f4b6bb7a99ef5e0ab440 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 17 Apr 2020 04:32:43 +0200 Subject: [PATCH 15/49] Logic version 2 (ready for review again) Add constraint functions + change the doctypes + change aqusition to asynchrone + change the test --- logic/spectrum_logic.py | 1591 ++++++++++++++------------------------- 1 file changed, 557 insertions(+), 1034 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index a0cf57aab2..e38a143229 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -23,7 +23,6 @@ from qtpy import QtCore from collections import OrderedDict import numpy as np -import matplotlib.pyplot as plt from core.connector import Connector from core.statusvariable import StatusVar @@ -32,7 +31,6 @@ from logic.generic_logic import GenericLogic from core.configoption import ConfigOption -from time import sleep from datetime import date @@ -56,11 +54,9 @@ class SpectrumLogic(GenericLogic): # declare status variables (camera attribute) : _read_mode = StatusVar('read_mode', 'FVB') - _number_of_track = StatusVar('number_of_track', 1) - _track_height = StatusVar('track_height', 50) - _track_offset = StatusVar('track_offset', 0) + _active_tracks = StatusVar('active_tracks', [240, 240]) - _acquistion_mode = StatusVar('acquistion_mode', 'SINGLE_SCAN') + _acquistion_mode = StatusVar('acquistion_mode', 'MULTI_SCAN') _exposure_time = StatusVar('exposure_time', 1e-4) _camera_gain = StatusVar('camera_gain', 1) _number_of_scan = StatusVar('number_of_scan', 1) @@ -87,34 +83,53 @@ def __init__(self, **kwargs): def on_activate(self): """ Initialisation performed during activation of the module. """ + self.spectrometer_device = self.spectrometer() self.camera_device = self.camera() self._save_logic = self.savelogic() + # hardware constraints : + spectro_constraints = self.spectrometer_device.get_constraints() + self._number_of_gratings = spectro_constraints['number_of_gratings'] + self._wavelength_limits = spectro_constraints['wavelength_limits'] + + camera_constraints = self.camera_device.get_constraints() + self._read_mode_list = camera_constraints['read_mode_list'] + self._acquisition_mode_list = camera_constraints['acquisition_mode_list'] + self._trigger_mode_list = camera_constraints['trigger_mode_list'] + self._shutter_mode_list = camera_constraints['shutter_mode_list'] + self._image_size = camera_constraints['image_size'] + self._pixel_size = camera_constraints['pixel_size'] + + # Spectrometer calibration using camera contraints parameters: + self.spectrometer_device.set_calibration(self._image_size[1], self._pixel_size[1], self._image_size[0]/2) + # declare spectrometer attributes : + # grating : self._grating = self.grating self._grating_offset = self.grating_offset + #wavelenght : self._center_wavelength = self.center_wavelength - self._wavelength_limit = self.wavelength_limit self._wavelength_range = self.wavelength_range - self.detector_offset = self._track_offset - self._input_port = self.input_port self._output_port = self.output_port self._input_slit_width = self.input_slit_width self._output_slit_width = self.output_slit_width # declare camera attributes : - self._image_size = self.image_size - self._pixel_size = self.pixel_size - self._superpixel_size, self._superimage_size, self._superimage_position = self.image_parameters + self._active_image = self._image_size self._shutter_mode = self.shutter_mode - self._cooler_ON = self.cooler_ON + self._cooler_status = self.cooler_status self._camera_temperature = self.camera_temperature + # QTimer for asynchrone execution : + self._timer = QtCore.QTimer() + self._timer.timeout.connect(self.acquire_data) + self._counter = 0 + def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ @@ -126,28 +141,26 @@ def on_deactivate(self): ############################################################################## def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): - """ - Method used to save the data using the savelogic module + """Method used to save the data using the savelogic module - :param data_type: - :param file_name: - :param save_path: - :return: nothing + @param data_type: (str) must be 'image', 'spectrum' or 'background' + @param file_name: (str) name of the saved file + @param save_path: (str) relative path where the file should be saved + @return: nothing """ data = OrderedDict() today = date.today() if data_type == 'image': data = self._image_data[:, :] - else: - if data_type == 'spectrum': - data['Wavelength (m)'] = self._spectrum_data[0, :] - data['PL intensity (u.a)'] = self._spectrum_data[1, :] - elif data_type == 'background': - data['Wavelength (m)'] = self._background_data[0, :] - data['PL intensity (u.a)'] = self._background_data[1, :] - else: - self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' - ' \'background\' or \'image\'') + if data_type == 'spectrum': + data['Wavelength (m)'] = self._spectrum_data[0, :] + data['PL intensity (u.a)'] = self._spectrum_data[1, :] + elif data_type == 'background': + data['Wavelength (m)'] = self._background_data[0, :] + data['PL intensity (u.a)'] = self._background_data[1, :] + else: + self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' + ' \'background\' or \'image\'') if file_name is not None: file_label = file_name else: @@ -163,86 +176,69 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): # Acquisition functions ############################################################################## - def start_acquisition(self): + def start_spectrum_acquisition(self): + """Start acquisition by lauching the timer signal calling the 'acquire_data' function. """ - Start acquisition method calling the start acquisition method from the hardware module and changing the - logic module state to 'locked'. - - :return: nothing - """ - self.camera_device.start_acquisition() + self._counter = 0 self.module_state.lock() + self._timer.start(1000 * self._scan_delay) #The argument of QTimer.start() is in ms - def stop_acquisition(self): - """ - Stop acquisition method calling the stop acquisition method from the hardware module and changing the - logic module state to 'unlocked'. - - :return: nothing + def acquire_data(self): + """Method acquiring data by using the camera hardware mathode 'start_acquistion'. This method is connected + to a timer signal : after timer start this slot is called with a period of a time delay. After a certain + number of call this method can stop the timer if not in 'LIVE' acquisition. """ - self.camera_device.stop_acquisition() - self.module_state.unlock() - self.log.info("Stop acquisition : module state is 'idle' ") - - def acquire_spectrum(self): - self.module_state.lock() - if self._acquistion_mode == 'LIVE_ACQUISITION': - while self.module_state() == 'locked': - self.shutter_mode(1) - self.camera_device.start_acquisition() - self.shutter_mode(0) - self._spectrum_data = self.acquired_data() - sleep(self._scan_delay) - else: - scans = np.empty(self._number_of_scan) - for i in range(0, self._number_of_scan): - self.shutter_mode(1) - self.camera_device.start_acquisition() - self.shutter_mode(0) - scans[i] = self.acquired_data() - sleep(self._scan_delay) + self.shutter_mode('OPEN') + self.camera_device.start_acquisition() + self.shutter_mode('CLOSE') + self._counter += 1 + if self._read_mode == 'IMAGE': + self._image_data = np.concatenate(self._image_data, self.acquired_data) + name = 'Image' + else: + self._spectrum_data = np.concatenate(self._spectrum_data, self.acquired_data) + name = 'Spectrum' + if (self._number_of_scan - 1 < self._counter) and self._acquisition_mode != 'LIVE': self.module_state.unlock() - self._spectrum_data = scans - self.log.info("Spectrum acquisition succeed ! Number of acquired scan : {} " - "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + self._timer.timeout.stop() + self.log.info("{} acquisition succeed ! Number of acquired scan : {} " + "/ Delay between each scan : {}".format(name, self._number_of_scan, self._scan_delay)) def acquire_background(self): - scans = np.empty(self._number_of_scan) - self.shutter_mode(0) # Force the shutter to be close during background acquisition process + """Method acquiring a background by closing the shutter and using the 'start_acquisition' method. + """ self.module_state.lock() - for i in range(0, self._number_of_scan): - self.camera_device.start_acquisition() - scans[i] = self.acquired_data() - sleep(self._scan_delay) + self.shutter_mode('CLOSE') + self.camera_device.start_acquisition() + self._background_data = self.acquired_data self.module_state.unlock() - self._background_data = scans - self.log.info("Background acquisition succeed ! Number of acquired scan : {} " - "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + self.log.info("Background acquired ") - def acquire_image(self): - scans = np.empty(self._number_of_scan) - self.module_state.lock() - for i in range(0, self._number_of_scan): - self.shutter_mode(1) - self.camera_device.start_acquisition() - self.shutter_mode(0) - scans[i] = self.acquired_data() - sleep(self._scan_delay) + def stop_acquisition(self): + """Method calling the stop acquisition method from the camera hardware module and changing the + logic module state to 'unlocked'. + """ + self.camera_device.stop_acquisition() self.module_state.unlock() - self._image_data = scans - self.log.info("Image acquisition succeed ! Number of acquired scan : {} " - "/ Delay between each scan : {}".format(self._number_of_scan, self._scan_delay)) + self._timer.timeout.stop() + self.log.info("Acquisition stopped : module state is 'idle' ") @property def spectrum_data(self): + """Getter method returning the spectrum data. + """ return self._spectrum_data @property def background_data(self): + """Getter method returning the background data. + """ return self._background_data @property def image_data(self): + """Getter method returning the image data. + """ return self._image_data ############################################################################## @@ -257,117 +253,67 @@ def image_data(self): @property def grating(self): - """ - Getter method returning the grating number used by the spectrometer. + """Getter method returning the grating number used by the spectrometer. - :return: @int active grating number or 0 if error + @return: (int) active grating number """ - grating_number = self.spectrometer_device.get_grating() - is_int = isinstance(grating_number, int) - if is_int: - self._grating = grating_number - return grating_number - else: - self.log.error('Your hardware getter function \'get_grating()\' is not returning an integer ') - return 0 + self._grating = self.spectrometer_device.get_grating() + return self._grating @grating.setter def grating(self, grating_number): - """ - Setter method setting the grating number to use by the spectrometer. - - :param grating_number: @int gating number to set active - :return: nothing - """ - number_gratings = self.spectrometer_device.get_number_gratings() - is_int = isinstance(grating_number, int) - is_in_range = 0 < grating_number < number_gratings - is_change = grating_number != self._grating - if is_int and is_in_range and is_change: - self.spectrometer_device.set_grating(grating_number) - self.log.info('Spectrometer grating has been changed correctly ') - self._grating = grating_number - else: - if not is_int: - self.log.debug('Grating parameter is not correct : it must be an integer ') - if not is_in_range: - self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' - .format(number_gratings-1)) - else: - self.log.info('Grating parameter has not been changed') + """Setter method setting the grating number to use by the spectrometer. + + @param grating_number: (int) gating number to set active + @return: nothing + """ + if not isinstance(grating_number, int): + self.log.debug('Grating parameter is not correct : it must be an integer ') + break + if not 0 < grating_number < self._number_of_gratings: + self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' + .format(self._number_of_gratings - 1)) + break + if not grating_number != self._grating: + self.log.info('Grating parameter has not been changed') + break + self.spectrometer_device.set_grating(grating_number) + self._grating = grating_number + self.log.info('Spectrometer grating has been changed correctly ') @property - def grating_offset(self, grating_number): - """ - Getter method returning the grating offset of the grating selected by the grating_number parameter. + def grating_offset(self): + """Getter method returning the grating offset of the grating selected by the grating_number parameter. - :param grating_number: @int grating number which correspond the offset - :return: @int the corresponding grating offset or 0 if error + @return: (int) the corresponding grating offset """ - number_gratings = self.spectrometer_device.get_number_gratings() - var_is_int = isinstance(grating_number, int) - var_is_in_range = 0 < grating_number < number_gratings - var_is_change = grating_number != self._grating - if var_is_int and var_is_in_range and var_is_change: - grating_offset = self.spectrometer_device.get_grating_offset() - is_int = isinstance(grating_number, int) - if is_int: - self._grating_offset = grating_offset - return grating_offset - else: - self.log.error('Your hardware getter function \'get_grating_offset()\' is not returning an integer ') - return 0 - else: - if not var_is_int: - self.log.debug('Grating parameter is not correct : it must be an integer ') - if not var_is_in_range: - self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' - .format(number_gratings-1)) - else: - self.log.info('Grating parameter has not been changed') - return 0 + self._grating_offset = self.spectrometer_device.get_grating_offset(self._grating) + return self._grating_offset @grating_offset.setter - def grating_offset(self, grating_number, grating_offset): - """ - Setter method setting the grating offset of the grating selected by the grating_number parameter. - - :param grating_number: @int grating number which correspond the offset - :param grating_offset: @int grating offset - :return: nothing - """ - number_gratings = self.spectrometer_device.get_number_gratings() - grating_is_int = isinstance(grating_number, int) - grating_is_in_range = -1 < grating_number < number_gratings - grating_is_change = grating_number != self._grating - grating_is_correct = grating_is_int and grating_is_in_range and grating_is_change - number_pixels = self.number_of_pixels() - offset_min = -number_pixels//2 - number_pixels % 2 - offset_max = number_pixels//2 - offset_is_int = isinstance(grating_offset, int) - offset_is_in_range = offset_min < grating_offset < offset_max - offset_is_change = grating_offset != self._grating_offset - offset_is_correct = offset_is_int and offset_is_in_range and offset_is_change - if grating_is_correct and offset_is_correct: - self.spectrometer_device.set_grating_offset(grating_offset) - self.log.info('Spectrometer grating offset has been changed correctly ') - self._grating = grating_number - else: - if not grating_is_int: - self.log.debug('Grating parameter is not correct : it must be an integer ') - elif not grating_is_in_range: - self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' - .format(number_gratings-1)) - elif not grating_is_change: - self.log.info('Grating parameter has not been changed') - elif not offset_is_int: - self.log.debug('Offset parameter is not correct : it must be an integer ') - elif not offset_is_in_range: - self.log.debug('Offset parameter is not correct : it must be in range {} to {} ' - .format(offset_min, offset_max)) - elif not offset_is_change: - self.log.info('Offset parameter has not been changed') + def grating_offset(self, grating_offset): + """Setter method setting the grating offset of the grating selected by the grating_number parameter. + + @param grating_number: (int) grating number which correspond the offset + @param grating_offset: (int) grating offset + @return: nothing + """ + if not isinstance(grating_offset, int): + self.log.debug('Offset parameter is not correct : it must be an integer ') + break + offset_min = -self._number_of_gratings//2 - self._number_of_gratings % 2 + offset_max = self._number_of_gratings//2 + if not offset_min < grating_offset < offset_max: + self.log.debug('Offset parameter is not correct : it must be in range {} to {} ' + .format(offset_min, offset_max)) + break + if not grating_offset != self._grating_offset: + self.log.info('Offset parameter has not been changed') + break + self._grating_offset = grating_offset + self.spectrometer_device.set_grating_offset(grating_offset) + self.log.info('Spectrometer grating offset has been changed correctly ') ############################################################################## # Wavelength functions @@ -375,222 +321,82 @@ def grating_offset(self, grating_number, grating_offset): @property def center_wavelength(self): - """ - Getter method returning the center wavelength of the measured spectral range. + """Getter method returning the center wavelength of the measured spectral range. - :return: @float the spectrum center wavelength or 0 if error + @return: (float) the spectrum center wavelength """ - wavelength = self.spectrometer_device.get_wavelength() - is_float = isinstance(wavelength, float) - if is_float: - self._center_wavelength = wavelength - return wavelength - else: - self.log.error('Your hardware getter function \'get_wavelength()\' is not returning a float ') - return 0 + self._center_wavelength = self.spectrometer_device.get_wavelength() + return self._center_wavelength @center_wavelength.setter def center_wavelength(self, wavelength): - """ - Setter method setting the center wavelength of the measured spectral range. - - :param wavelength: @float center wavelength - :return: nothing - """ - wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) - is_float = isinstance(wavelength, float) - is_in_range = wavelength_min < wavelength < wavelength_max - is_change = wavelength != self._center_wavelength - if is_float and is_in_range and is_change: - self.spectrometer_device.set_wavelength(wavelength) - self.log.info('Spectrometer wavelength has been changed correctly ') - self._center_wavelength = wavelength - else: - if not is_float: - self.log.debug('Wavelength parameter is not correct : it must be a float ') - elif not is_in_range: - self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' - .format(wavelength_min, wavelength_max)) - else: - self.log.info('Wavelength parameter has not been changed') - - @property - def wavelength_limit(self): - wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) - is_float = isinstance(wavelength_min, float) and isinstance(wavelength_max, float) - is_sort = 0 < wavelength_min < wavelength_max - if is_float and is_sort: - self._wavelength_limit = (wavelength_min, wavelength_max) - return (wavelength_min, wavelength_max) - else: - if not is_float: - self.log.error("Your hardware getter function 'get_wavelength_limit()' is not returning a " - "float type tuple ") - if is_sort: - self.log.error("Your hardware getter function 'get_wavelength_limit()' is not returning a " - "sorted values or negative values ") + """Setter method setting the center wavelength of the measured spectral range. + + @param wavelength: (float) center wavelength + @return: nothing + """ + if not isinstance(wavelength, float): + self.log.debug('Wavelength parameter is not correct : it must be a float ') + break + wavelength_min = self._wavelength_limits[self._grating, 0] + wavelength_max = self._wavelength_limits[self._grating, 1] + if not wavelength_min < wavelength < wavelength_max: + self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' + .format(wavelength_min, wavelength_max)) + break + if not wavelength != self._center_wavelength: + self.log.info('Wavelength parameter has not been changed') + break + self._center_wavelength = wavelength + self.spectrometer_device.set_wavelength(wavelength) + self.log.info('Spectrometer wavelength has been changed correctly ') @property def wavelength_range(self): - """ - Getter method returning the wavelength array of the full measured spectral range. + """Getter method returning the wavelength array of the full measured spectral range. (used for plotting spectrum with the spectral range) - :return: @ndarray measured wavelength array or 0 if error - """ - wavelength_min, wavelength_max = self.spectrometer_device.get_wavelength_limit(self._grating) - wavelength_range = self.spectrometer_device.get_calibration(self._pixel_matrix_dimension[0]) - is_ndarray = isinstance(wavelength_range, np.ndarray) - is_in_range = np.min(wavelength_range) > wavelength_min and np.max(wavelength_range) > wavelength_max - if is_ndarray and is_in_range: - self._wavelength_range = wavelength_range - return wavelength_range - else: - if not is_ndarray: - self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ndarray ') - else: - self.log.error('Your hardware getter function \'get_calibration()\' is not returning a ' - 'wavelength in range of the current grating wavelength limits ') - return 0 - - @property - def number_of_pixels(self): - """ - Getter method returning the number of pixels used by the spectrometer DLLs calibration function. - (the value return by this function must match with the real pixel number of the camera) - - :return: @int number of pixels or 0 if error - """ - number_pixels = self.spectrometer_device.get_number_of_pixels() - is_int = isinstance(number_pixels, int) - is_positive = 0 < number_pixels - if is_int and is_positive: - self._pixel_matrix_dimension[0] = number_pixels - return number_pixels - else: - if not is_int: - self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a int ') - else: - self.log.error('Your hardware getter function \'get_number_of_pixels()\' is not returning a ' - 'positive number ') - return 0 - - @number_of_pixels.setter - def number_of_pixels(self, number_pixels): - """ - Setter method setting the number of pixels used by the spectrometer DLLs calibration function. - (the value set by this function must be the real pixel number of the camera) - - :param number_pixels: @int number of pixels - :return: nothing - """ - is_int = isinstance(number_pixels, int) - is_positive = 0 < number_pixels - is_change = number_pixels != self._pixel_matrix_dimension[0] - if is_int and is_positive and is_change: - self.spectrometer_device.get_pixel_width(number_pixels) - self.log.info('Number of pixels has been changed correctly ') - self._pixel_matrix_dimension[0] = number_pixels - else: - if not is_int: - self.log.debug('Number of pixels parameter is not correct : it must be a int ') - elif not is_positive: - self.log.debug('Number of pixels parameter is not correct : it must be positive ') - else: - self.log.info('Number of pixels parameter has not been changed') - - @property - def pixel_width(self): - """ - Getter method returning the pixel width used by the spectrometer DLLs calibration function. - (the value returned by this function must match the real pixel width of the camera) - - :return: @int pixel width or 0 if error + @return: (ndarray) measured wavelength array """ - pixel_width = self.spectrometer_device.get_pixel_width() - is_float = isinstance(pixel_width, float) - is_positive = 0 < pixel_width - if is_float and is_positive: - return pixel_width - else: - if not is_float: - self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a float ') - else: - self.log.error('Your hardware getter function \'get_pixel_width()\' is not returning a ' - 'positive number ') - return 0 - - @pixel_width.setter - def pixel_width(self, pixel_width): - """ - Setter method setting the pixel width used by the spectrometer DLLs calibration function. - (the value set by this function must be the real pixel width of the camera) - - :param pixel_width: @int pixel width - :return: nothing - """ - is_float = isinstance(pixel_width, float) - is_positive = 0 < pixel_width - is_change = pixel_width != self._pixel_size[0] - if is_float and is_positive and is_change: - self.spectrometer_device.set_pixel_width(pixel_width) - self.log.info('Pixel width has been changed correctly ') - else: - if not is_float: - self.log.debug('Pixel width parameter is not correct : it must be a float ') - elif not is_positive: - self.log.debug('Pixel width parameter is not correct : it must be positive ') - else: - self.log.info('Pixel width parameter has not been changed') + self._wavelength_range = self.spectrometer_device.get_calibration(self._pixel_matrix_dimension[0]) + return self._wavelength_range ############################################################################## - # Detector functions + # Calibration functions ############################################################################## - @property - def detector_offset(self): - """ - Getter method returning the detector offset used by the spectrometer DLLs calibration function. - (the value returned by this function must match the real detector offset value of the camera) - - :return: @int detector offset or 0 error - """ - offset = self.spectrometer_device.get_detector_offset() - is_int = isinstance(offset, int) - if is_int: - self._track_offset = offset - return offset - else: - self.log.error('Your hardware getter function \'get_detector_offset()\' is not returning a int ') - return 0 - - @detector_offset.setter - def detector_offset(self, detector_offset): - """ - Setter method returning the detector offset used by the spectrometer DLLs calibration function. + def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): + """Setter method returning the detector offset used by the spectrometer DLLs calibration function. (the value returned by this function must be the real detector offset value of the camera) - :param detector_offset: @int detetcor offset - :return: nothing - """ - number_pixels = self._image_size[0] - offset_min = -number_pixels//2 - 1 - offset_max = number_pixels//2 - is_int = isinstance(detector_offset, int) - is_in_range = offset_min - 1 < detector_offset < offset_max + 1 - is_change = detector_offset != self._track_offset - if is_int and is_in_range and is_change: - self.spectrometer_device.set_detector_offset(detector_offset) - self.log.info('Detector offset has been changed correctly ') - self._track_offset = detector_offset - else: - if not is_int: - self.log.debug('Detector offset parameter is not correct : it must be a int ') - elif not is_in_range: - self.log.debug('Detector offset parameter is not correct : it must be in range {} to {} ' - .format(offset_min, offset_max)) - else: - self.log.info('Detector offset parameter has not been changed') + @param number_of_pixels: (int) number of pixels + @param pixel_width: (float) pixel width + @param tracks_offset: (int) tracks offset + @return: nothing + """ + if not isinstance(number_of_pixels, int): + self.log.debug('Number_of_pixels parameter is not correct : it must be a int ') + break + if not isinstance(pixel_width, float): + self.log.debug('Pixel_width parameter is not correct : it must be a float ') + break + if not isinstance(tracks_offset, int): + self.log.debug('Tracks_offset parameter is not correct : it must be a int ') + break + if number_of_pixels < 0: + self.log.debug('Number_of_pixels parameter must be positive ') + break + if pixel_width < 0: + self.log.debug('Pixel_width parameter must be positive ') + break + offset_min = -self._image_size[0]//2 - 1 + offset_max = self._image_size[0]//2 + if not offset_min - 1 < tracks_offset < offset_max + 1: + self.log.debug('Tracks_offset parameter is not correct : it must be in range {} to {} ' + .format(offset_min, offset_max)) + break + self.spectrometer_device.set_calibration(number_of_pixels, pixel_width, tracks_offset) + self.log.info('Calibration parameters have been set correctly ') ############################################################################## # Ports and Slits functions @@ -598,178 +404,120 @@ def detector_offset(self, detector_offset): @property def input_port(self): - """ - Getter method returning the active current input port of the spectrometer. + """Getter method returning the active current input port of the spectrometer. - :return: @int active input port (0 front and 1 side) or 0 if error + @return: (int) active input port (0 front and 1 side) """ - input_port = self.spectrometer_device.get_input_port() - is_int = isinstance(input_port, int) - is_in_range = -1 < input_port < 2 - if is_int and is_in_range: - self._input_port = input_port - return input_port - else: - if not is_int: - self.log.error('Your hardware getter function \'get_input_port()\' is not returning a int ') - else: - self.log.error('Your hardware getter function \'get_input_port()\' is not in range 0 to 1 ') - return 0 + self._input_port = self.spectrometer_device.get_input_port() + return self._input_port @input_port.setter def input_port(self, input_port): - """ - Setter method setting the active current input port of the spectrometer. - - :param input_port: input port - :return: nothing - """ - side_port_possible = self.spectrometer_device.flipper_mirror_is_present(1) - is_int = isinstance(input_port, int) - is_in_range = -1 < input_port < 2 - is_change = input_port != self._input_port - if is_int and is_in_range and is_change: - if side_port_possible or input_port == 0: - self.spectrometer_device.set_input_port(input_port) - self.log.info('Input port has been changed correctly ') - self._input_port = input_port - else: - self.log.debug('Your hardware do not have any flipper mirror present at the input port ') - else: - if not is_int: - self.log.debug('Input port parameter is not correct : it must be a int ') - elif not is_in_range: - self.log.debug('Input port parameter is not correct : it must be 0 or 1 ') - else: - self.log.info('Input port parameter has not been changed') + """Setter method setting the active current input port of the spectrometer. + + @param input_port: (int) active input port (0 front and 1 side) + @return: nothing + """ + if input_port==1 and not self.spectrometer_device.flipper_mirror_is_present(1): + self.log.debug('Your hardware do not have any flipper mirror present at the input port ') + break + if not isinstance(input_port, int): + self.log.debug('Input port parameter is not correct : it must be a int ') + break + if not -1 < input_port < 2: + self.log.debug('Input port parameter is not correct : it must be 0 or 1 ') + break + if not input_port != self._input_port: + self.log.info('Input port parameter has not been changed') + break + self._input_port = input_port + self.spectrometer_device.set_input_port(input_port) + self.log.info('Input port has been changed correctly ') @property def output_port(self): - """ - Getter method returning the active current output port of the spectrometer. + """Getter method returning the active current output port of the spectrometer. - :return: @int active output port (0 front and 1 side) or 0 if error + @return: (int) active output port (0 front and 1 side) """ - output_port = self.spectrometer_device.get_output_port() - is_int = isinstance(output_port, int) - is_in_range = -1 < output_port < 2 - if is_int and is_in_range: - self._input_port = output_port - return output_port - else: - if not is_int: - self.log.error('Your hardware getter function \'get_output_port()\' is not returning a int ') - else: - self.log.error('Your hardware getter function \'get_output_port()\' is not in range 0 to 1 ') - return 0 + self._output_port = self.spectrometer_device.get_output_port() + return self._output_port @output_port.setter def output_port(self, output_port): """ Setter method setting the active current output port of the spectrometer. - :param output_port: output port - :return: nothing - """ - side_port_possible = self.spectrometer_device.flipper_mirror_is_present(2) - is_int = isinstance(output_port, int) - is_in_range = -1 < output_port < 2 - is_change = output_port != self._output_port - if is_int and is_in_range and is_change: - if side_port_possible or output_port == 0: - self.spectrometer_device.set_output_port(output_port) - self.log.info('Output port has been changed correctly ') - self._output_port = output_port - else: - self.log.debug('Your hardware do not have any flipper mirror present at the output port ') - else: - if not is_int: - self.log.debug('Output port parameter is not correct : it must be a int ') - elif not is_in_range: - self.log.debug('Output port parameter is not correct : it must be 0 or 1 ') - else: - self.log.info('Output port parameter has not been changed') + @param output_port: (int) active output port (0 front and 1 side) + @return: nothing + """ + if output_port==1 and not self.spectrometer_device.flipper_mirror_is_present(1): + self.log.debug('Your hardware do not have any flipper mirror present at the output port ') + break + if not isinstance(output_port, int): + self.log.debug('Output port parameter is not correct : it must be a int ') + break + if not -1 < output_port < 2: + self.log.debug('Output port parameter is not correct : it must be 0 or 1 ') + break + if not output_port != self._output_port: + self.log.info('Output port parameter has not been changed') + break + self._output_port = output_port + self.spectrometer_device.set_output_port(output_port) + self.log.info('Output port has been changed correctly ') @property def input_slit_width(self): - """ - Getter method returning the active input port slit width of the spectrometer. + """Getter method returning the active input port slit width of the spectrometer. - :return: @float input port slit width or 0 if error + @return: (float) input port slit width """ - slit_width = self.spectrometer_device.get_auto_slit_width('input', self._input_port) - is_float = isinstance(slit_width, float) - if is_float: - self._input_slit_width = slit_width - return slit_width - else: - self.log.error('Your hardware getter function \'get_auto_slit_width()\' is not returning a float ') - return 0 + self._input_slit_width = self.spectrometer_device.get_auto_slit_width('input', self._input_port) + return self._input_slit_width @input_slit_width.setter def input_slit_width(self, slit_width): - """ - Setter method setting the active input port slit width of the spectrometer. + """Setter method setting the active input port slit width of the spectrometer. - :param slit_width: @float input port slit width - :return: nothing + @param slit_width: (float) input port slit width + @return: nothing """ - slit_is_present = self.spectrometer_device.auto_slit_is_present('input', self._input_port) - is_float = isinstance(slit_width, float) - is_change = slit_width == self._input_slit_width - if is_float and is_change: - if slit_is_present: - self.spectrometer_device.set_auto_slit_width('input', self._input_port, slit_width) - self.log.info('Output slit width has been changed correctly ') - self._input_slit_width = slit_width - else: - self.log.debug('Your hardware do not have any auto slit present at the selected input port ') - else: - if not is_float: - self.log.debug('Input slit width parameter is not correct : it must be a float ') - else: - self.log.info("Input slit width parameter has not be changed ") - + if not isinstance(slit_width, float): + self.log.debug('Input slit width parameter is not correct : it must be a float ') + break + if slit_width == self._input_slit_width: + self.log.info("Input slit width parameter has not be changed ") + break + self._input_slit_width = slit_width + self.spectrometer_device.set_auto_slit_width('input', self._input_port, slit_width) + self.log.info('Output slit width has been changed correctly ') @property def output_slit_width(self): - """ - Getter method returning the active output port slit width of the spectrometer. + """Getter method returning the active output port slit width of the spectrometer. - :return: @float output port slit width or 0 if error + @return: (float) output port slit width """ - slit_width = self.spectrometer_device.get_auto_slit_width('output', self._output_port) - is_float = isinstance(slit_width, float) - if is_float: - self._output_slit_width = slit_width - return slit_width - else: - self.log.error('Your hardware getter function \'get_auto_slit_width()\' is not returning a float ') - return 0 + self._output_slit_width = self.spectrometer_device.get_auto_slit_width('output', self._output_port) + return self._output_slit_width @output_slit_width.setter def output_slit_width(self, slit_width): - """ - Setter method setting the active output port slit width of the spectrometer. + """Setter method setting the active output port slit width of the spectrometer. - :param slit_width: @float output port slit width - :return: nothing + @param slit_width: (float) output port slit width + @return: nothing """ - slit_is_present = self.spectrometer_device.auto_slit_is_present('output', self._output_port) - is_float = isinstance(slit_width, float) - is_change = slit_width == self._output_slit_width - if is_float and is_change: - if slit_is_present: - self.spectrometer_device.set_auto_slit_width('output', self._output_port, slit_width) - self.log.info('Output slit width has been changed correctly ') - self._output_slit_width = slit_width - else: - self.log.debug('Your hardware do not have any auto slit present at the selected output port ') - else: - if not is_float: - self.log.debug('Output slit width parameter is not correct : it must be a float ') - else: - self.log.info("Output slit width parameter has not be changed ") + if not isinstance(slit_width, float): + self.log.debug('Output slit width parameter is not correct : it must be a float ') + break + if slit_width == self._output_slit_width: + self.log.info("Output slit width parameter has not be changed ") + break + self._output_slit_width = slit_width + self.spectrometer_device.set_auto_slit_width('output', self._output_port, slit_width) + self.log.info('Output slit width has been changed correctly ') ############################################################################## @@ -784,88 +532,26 @@ def output_slit_width(self, slit_width): @property def image_size(self): - image_size = self.camera_device.get_image_size() - is_correct = len(image_size) == 2 - is_int = isinstance(image_size[0], int) and isinstance(image_size[1], int) - is_pos = image_size[0]>0 and image_size[1]>0 - if is_correct and is_int and is_pos: - self._image_size = image_size - return image_size - else: - if not is_correct: - self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of 2 elements ") - if not is_int: - self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of int ") - else: - self.log.error("Your hardware method 'get_image_size()' is not returning a tuple of positive number ") - return 0 + """Getter method returning the real pixel matrix dimensions of the camera. + """ + self._image_size = self.camera_device.get_image_size() + return self._image_size @property def pixel_size(self): - pixel_size = self.camera_device.get_pixel_size() - is_correct = len(pixel_size) == 2 - is_float = isinstance(pixel_size[0], float) and isinstance(pixel_size[1], float) - is_pos = pixel_size[0]>0 and pixel_size[1]>0 - if is_correct and is_float and is_pos: - self._pixel_size = pixel_size - return pixel_size - else: - if not is_correct: - self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of 2 elements ") - if not is_float: - self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of float ") - else: - self.log.error("Your hardware method 'get_pixel_size()' is not returning a tuple of positive number ") - return 0 - + """Getter method returning the real pixel dimensions of the camera. + """ + self._pixel_size = self.camera_device.get_pixel_size() + return self._pixel_size @property def acquired_data(self): """ Return an array of last acquired image. - @return numpy array: image data in format [[row],[row]...] + @return: (ndarray) image data in format [[row],[row]...] Each pixel might be a float, integer or sub pixels """ - data = self.camera_device.get_acquired_data() - data = np.array(data) # Data are forced to be a ndarray - is_ndarray = isinstance(data, np.ndarray) - is_float = isinstance(data.dtype, float) - if is_ndarray and is_float: - if self._read_mode == 'IMAGE': - is_correct_dim = np.shape(data)[0, 2] == self._superimage_size - if not is_correct_dim: - data = data[::-1] - is_correct_dim = np.shape(data)[0, 2] == self._superimage_size - elif self._read_mode == 'MULTI_TRACK': - is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) - if not is_correct_dim: - data = data[::-1] - is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) - else: - is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) - if not is_correct_dim: - data = data[::-1] - is_correct_dim = np.shape(data)[0, 2] == (self._image_size[1], self._number_of_track) - if is_correct_dim: - return data - else: - self.log.error("Your hardware function 'get_acquired_data()'" - " is not returning a numpy array with the expected dimension ") - if not is_float : - self.log.debug("Your hardware function 'get_acquired_data()'" - " is not returning a numpy array with dtype float ") - else: - self.log.debug("Your hardware function 'get_acquired_data()' is not returning a numpy array ") - return np.empty((2, 0)) - - - @property - def ready_state(self): - """ Is the camera ready for an acquisition ? - - @return bool: ready ? - """ - pass + return self.camera_device.get_acquired_data() ############################################################################## # Read mode functions @@ -873,173 +559,110 @@ def ready_state(self): @property def read_mode(self): - """ - Getter method returning the current read mode used by the camera. + """Getter method returning the current read mode used by the camera. - :return: @str read mode (must be compared to a dict) + @return: (str) read mode (must be compared to the list) """ - read_mode = self.camera_device.get_read_mode() - is_str = isinstance(read_mode, str) - if is_str: - self._read_mode = read_mode - return read_mode - else: - self.log.error("Your hardware method 'get_read_mode()' is not returning a string ") - return 0 + self._read_mode = self.camera_device.get_read_mode() + return self._read_mode @read_mode.setter def read_mode(self, read_mode): - """ - Setter method setting the read mode used by the camera. - - :param read_mode: @str read mode (must be compared to a dict) - :return: nothing - """ - is_str = isinstance(read_mode, str) - is_change = read_mode == self._read_mode - if is_str and is_change: - self.camera_device.set_read_mode(read_mode) - else: - if not is_str: - self.log.debug("Read mode parameter must be a string ") - else: - self.log.info("Read mode parameter has not be changed ") + """Setter method setting the read mode used by the camera. + + @param read_mode: (str) read mode (must be compared to the list) + @return: nothing + """ + if not read_mode in self._read_mode_list: + self.log.debug("Read mode parameter do not match with any of the available read " + "mode of the camera ") + break + if not isinstance(read_mode, str): + self.log.debug("Read mode parameter must be a string ") + break + if not read_mode == self._read_mode: + self.log.info("Read mode parameter has not be changed ") + break + self.camera_device.set_read_mode(read_mode) @property - def track_parameters(self): - """ - Getter method returning the read mode tracks parameters of the camera. + def active_tracks(self): + """Getter method returning the read mode tracks parameters of the camera. - :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error + @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] """ - track_parameters = self.camera_device.get_track_parameters() - are_int = all([isinstance(track_parameter, int) for track_parameter in track_parameters]) - are_pos = all([isinstance(track_parameter, int) for track_parameter in track_parameters[:2]]) - are_in_range = len(track_parameters)==3 - number_of_track, track_height, track_offset = track_parameters - if are_int and are_pos and are_in_range: - self._number_of_track = number_of_track - self._track_height = track_height - self._track_offset = track_offset - return track_parameters - else: - if not are_int: - self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of int ") - if not are_in_range: - self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of size 3 ") - else: - self.log.error("Your hardware method 'get_track_parameters()' is not returning a tuple of " - "positive numbers ") - return 0 - - @track_parameters.setter - def track_parameters(self, number_of_track, track_height, track_offset): + self._active_tracks = self.camera_device.get_active_tracks() + return self._active_tracks + + @active_tracks.setter + def active_tracks(self, active_tracks): """ Setter method setting the read mode tracks parameters of the camera. - :param number_of_track: @int number of track - :param track_height: @int track height - :param track_offset: @int track offset - :return: nothing - """ - are_int = isinstance(number_of_track, int) and isinstance(track_height, int) and isinstance(track_offset, int) - are_pos = number_of_track>0 and track_height>0 - number_is_change = number_of_track == self._number_of_track - height_is_change = track_height == self._track_height - offset_is_change = track_offset == self._track_offset - if are_int and are_pos and (number_is_change or height_is_change or offset_is_change): - track_spacing = self._image_size[0]/(number_of_track + 2 - number_of_track%2) - number_is_correct = number_of_track <= self._image_size[0] - height_is_correct = track_height <= track_spacing - offset_is_correct = (number_of_track*track_spacing+track_height)/2 < self._image_size[0]-abs(track_offset) - if number_is_correct and height_is_correct and offset_is_correct: - self.camera_device.set_track_parameters(number_of_track, track_height, track_offset) - self._number_of_track = number_of_track - self._track_height = track_height - self._track_offset = track_offset - self.log.info("Track parameters has been set properly ") - else: - if not number_is_correct: - self.log.debug("Number of track parameter must be less than the camera number of pixels ") - if not height_is_correct: - self.log.debug("Track height parameter must be lower than track inter-spacing : " - "the tracks are covering each other ") - else: - self.log.debug("Track offset parameter must be set in the way to keep tracks inside " - "the pixel matrix : some tracks are outside the pixel area ") - else: - if not are_int: - self.log.debug("Track parameters must be integers ") - if not are_pos: - self.log.debug("Number of track and track height parameters must be positive ") - else: - self.log.info("Track parameters has not be changed ") + @param active_tracks: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @return: nothing + """ + if not (np.all(active_tracks[::2] 0 for image_parameter in image_parameters]) - are_in_range = len(image_parameters) == 3 - superpixel_size, superimage_size, superimage_position = image_parameters - if are_int and are_pos and are_in_range: - self._superpixel_size = superpixel_size - self._superimage_size = superimage_size - self._superimage_position = superimage_position - return image_parameters - else: - if not are_int: - self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of int ") - if not are_in_range: - self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of size 3 ") - else: - self.log.error("Your hardware method 'get_image_parameters()' is not returning a tuple of " - "positive numbers ") - return 0 - - @image_parameters.setter - def image_parameters(self, superpixel_size, superimage_size, superimage_position): + self._active_image = self.camera_device.get_active_image() + return self._active_image + + @active_image.setter + def active_image(self, hbin, vbin, hstart, hend, vstart, vend): """ Setter method setting the read mode image parameters of the camera. - :param pixel_height: @int pixel height - :param pixel_width: @int pixel width - :param raw_range: @tuple (@int start raw, @int end raw) - :param column_range: @tuple (@int start column, @int end column) - :return: nothing - """ - image_parameters = ( superpixel_size, superimage_size, superimage_position) - are_int = all([isinstance(image_parameter, int) for image_parameter in image_parameters]) - are_pos = all([image_parameter > 0 for image_parameter in image_parameters]) - pixel_is_change = superpixel_size == self._superpixel_size - image_is_change = superimage_size == self._superimage_size - image_pos_is_change = superimage_position == self._superimage_position - if are_int and are_pos and (pixel_is_change or image_is_change or image_pos_is_change): - superraw_is_correct = superpixel_size[0]*superimage_size[0]0 - is_in_range = self._exposure_time < accumulation_delay < self._scan_delay - if is_float and is_pos and is_in_range: - self._accumulation_delay = accumulation_delay - return accumulation_delay - else: - if not is_float: - self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a float ") - if not is_pos: - self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a positive number ") - else: - self.log.error("Your hardware method 'get_accumulation_delay()' is not returning a value between" - "the current exposure time and scan delay values ") - return 0 + self._accumulation_delay = self.camera_device.get_accumulation_delay() + return self._accumulation_delay @accumulation_delay.setter def accumulation_delay(self, accumulation_delay): - """ - Setter method setting the accumulation cycle time scan carry out during an accumulate acquisition mode - by the camera. - - :param accumulation_delay: @int accumulation cycle time - :return: nothing - """ - is_float = isinstance(accumulation_delay, float) - is_pos = accumulation_delay > 0 - is_in_range = self._exposure_time < accumulation_delay < self._scan_delay - is_change = accumulation_delay == self._accumulation_delay - if is_float and is_pos and is_in_range and is_change: - self._accumulation_delay = accumulation_delay - self.camera_device.set_accumulation_delay(accumulation_delay) - else: - if not is_float: - self.log.debug("Accumulation time parameter must be a float ") - if not is_pos: - self.log.debug("Accumulation time parameter must be a positive number ") - if not is_in_range: - self.log.debug("Accumulation time parameter must be a value between" - "the current exposure time and scan delay values ") - else: - self.log.info("Accumulation time parameter has not be changed ") + """Setter method setting the accumulation delay between consecutive scan during an accumulate acquisition mode. + + @param accumulation_delay: (float) accumulation delay + @return: nothing + """ + if not isinstance(accumulation_delay, float): + self.log.debug("Accumulation time parameter must be a float ") + break + if not accumulation_delay > 0 : + self.log.debug("Accumulation time parameter must be a positive number ") + break + if not self._exposure_time < accumulation_delay < self._scan_delay: + self.log.debug("Accumulation time parameter must be a value between" + "the current exposure time and scan delay values ") + break + if not accumulation_delay == self._accumulation_delay: + self.log.info("Accumulation time parameter has not be changed ") + break + self._accumulation_delay = accumulation_delay + self.camera_device.set_accumulation_delay(accumulation_delay) + self.log.info('Accumulation delay has been set correctly ') @property def number_accumulated_scan(self): - """ - Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. + """Getter method returning the number of accumulated scan during accumulate acquisition mode. - :return: @int number of accumulated scan or 0 if error + @return: (int) number of accumulated scan """ - number_scan = self.camera_device.get_number_accumulated_scan() - is_int = isinstance(number_scan, int) - is_pos = number_scan > 0 - if is_int and is_pos: - self._number_accumulated_scan = number_scan - return number_scan - else: - if not is_int: - self.log.error("Your hardware method 'get_number_accumulated_scan()' is not returning a int ") - else: - self.log.error("Your hardware method 'get_number_accumulated_scan()' is not returning a positive number ") - return 0 + self._number_accumulated_scan = self.camera_device.get_number_accumulated_scan() + return self._number_accumulated_scan @number_accumulated_scan.setter def number_accumulated_scan(self, number_scan): - """ - Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. - - :param number_scan: @int number of accumulated scan - :return: nothing - """ - is_int = isinstance(number_scan, int) - is_pos = number_scan > 0 - is_change = number_scan == self._number_of_scan - if is_int and is_pos and is_change: - self._number_accumulated_scan = number_scan - self.camera_device.set_number_accumulated_scan(number_scan) - else: - if not is_int: - self.log.debug("Number of accumulated scan parameter must be an integer ") - if not is_pos: - self.log.debug("Number of accumulated scan parameter must be positive ") - else: - self.log.info("Number of scan parameter has not be changed ") + """Setter method setting the number of accumulated scan during accumulate acquisition mode. + + @param number_scan: (int) number of accumulated scan + @return: nothing + """ + if not isinstance(number_scan, int): + self.log.debug("Number of accumulated scan parameter must be an integer ") + break + if not number_scan > 0: + self.log.debug("Number of accumulated scan parameter must be positive ") + break + if not number_scan == self._number_of_scan: + self.log.info("Number of accumulated scan parameter has not be changed ") + break + self._number_accumulated_scan = number_scan + self.camera_device.set_number_accumulated_scan(number_scan) + self.log.info('Number of accumulated scan has been set correctly ') @property def exposure_time(self): """ Get the exposure time in seconds - @return float exposure time + @return: (float) exposure time """ - exposure_time = self.camera_device.get_exposure_time() - is_float = isinstance(exposure_time, float) - is_pos = exposure_time > 0 - is_in_range = exposure_time < self._accumulation_delay - if is_float and is_pos and is_in_range: - self._exposure_time = exposure_time - return exposure_time - else: - if not is_float: - self.log.error("Your hardware method 'get_exposure_time()' is not returning a float ") - if not is_pos: - self.log.error("Your hardware method 'get_exposure_time()' is not returning a positive number ") - else: - self.log.error("Your hardware method 'get_exposure_time()' is not returning a value lower" - "that the current accumulation time value ") - return 0 + self._exposure_time = self.camera_device.get_exposure_time() + return self._exposure_time @exposure_time.setter def exposure_time(self, exposure_time): - """ Set the exposure time in seconds - - @param float time: desired new exposure time - - @return float: setted new exposure time - """ - is_float = isinstance(exposure_time, float) - is_pos = exposure_time > 0 - is_in_range = exposure_time < self._accumulation_delay - is_change = exposure_time == self._exposure_time - if is_float and is_pos and is_in_range and is_change: - self._exposure_time = exposure_time - self.camera_device.set_exposure_time(exposure_time) - else: - if not is_float: - self.log.debug("Exposure time parameter must be a float ") - if not is_pos: - self.log.debug("Exposure time parameter must be a positive number ") - if not is_in_range: - self.log.debug("Exposure time parameter must be a value lower" - "that the current accumulation time values ") - else: - self.log.info("Exposure time parameter has not be changed ") + """ Set the exposure time in seconds. + + @param exposure_time: (float) desired new exposure time + + @return: nothing + """ + if not isinstance(exposure_time, float): + self.log.debug("Exposure time parameter must be a float ") + break + if not exposure_time > 0: + self.log.debug("Exposure time parameter must be a positive number ") + break + if not exposure_time < self._accumulation_delay: + self.log.debug("Exposure time parameter must be a value lower" + "that the current accumulation time values ") + break + if not exposure_time == self._exposure_time: + self.log.info("Exposure time parameter has not be changed ") + break + self._exposure_time = exposure_time + self.camera_device.set_exposure_time(exposure_time) + self.log.info('Exposure time has been set correctly ') @property def camera_gain(self): - """ Get the gain + """ Get the gain. - @return float: exposure gain + @return: (float) exposure gain """ - camera_gain = self.camera_device.get_camera_gain() - is_float = isinstance(camera_gain, float) - is_pos = camera_gain > 0 - if is_float and is_pos : - self._camera_gain = camera_gain - return camera_gain - else: - if not is_float: - self.log.error("Your hardware method 'get_camera_gain()' is not returning a float ") - else: - self.log.error("Your hardware method 'get_camera_gain()' is not returning a positive number ") - return 0 + self._camera_gain = self.camera_device.get_camera_gain() + return self._camera_gain @camera_gain.setter def camera_gain(self, camera_gain): - """ Set the gain + """ Set the gain. - @param float gain: desired new gain + @param camera_gain: (float) desired new gain - @return float: new exposure gain + @return: nothing """ - is_float = isinstance(camera_gain, float) - is_pos = camera_gain > 0 - is_change = camera_gain == self._camera_gain - if is_float and is_pos and is_change: - self._camera_gain = camera_gain - self.camera_device.set_camera_gain(camera_gain) - else: - if not is_float: - self.log.debug("Camera gain parameter must be a float ") - if not is_pos: - self.log.debug("Camera gain parameter must be a positive number ") - else: - self.log.info("Camera gain parameter has not be changed ") + if not isinstance(camera_gain, float): + self.log.debug("Camera gain parameter must be a float ") + break + if not camera_gain > 0: + self.log.debug("Camera gain parameter must be a positive number ") + break + if not camera_gain == self._camera_gain: + self.log.info("Camera gain parameter has not be changed ") + self._camera_gain = camera_gain + self.camera_device.set_camera_gain(camera_gain) + self.log.info('Camera gain has been set correctly ') ############################################################################## # Trigger mode functions @@ -1273,38 +829,32 @@ def camera_gain(self, camera_gain): @property def trigger_mode(self): - """ - Getter method returning the current trigger mode used by the camera. + """Getter method returning the current trigger mode used by the camera. - :return: @str trigger mode (must be compared to a dict) + @return: (str) trigger mode (must be compared to the list) """ - trigger_mode = self.camera_device.get_trigger_mode() - is_str = isinstance(trigger_mode, str) - if is_str: - self._trigger_mode = trigger_mode - return trigger_mode - else: - self.log.error("Your hardware method 'get_trigger_mode()' is not returning a string ") - return 0 + self._trigger_mode = self.camera_device.get_trigger_mode() + return self._trigger_mode @trigger_mode.setter def trigger_mode(self, trigger_mode): - """ - Setter method setting the trigger mode used by the camera. - - :param trigger_mode: @str trigger mode (must be compared to a dict) - :return: nothing - """ - is_str = isinstance(trigger_mode, str) - is_change = trigger_mode == self._trigger_mode - if is_str and is_change: - self._trigger_mode = trigger_mode - self.camera_device.set_trigger_mode(trigger_mode) - else: - if not is_str: - self.log.debug("Trigger mode parameter must be a string ") - else: - self.log.info("Trigger mode parameter has not be changed ") + """Setter method setting the trigger mode used by the camera. + + @param trigger_mode: (str) trigger mode (must be compared to the list) + @return: nothing + """ + if not trigger_mode in self._trigger_mode_list: + self.log.debug("Trigger mode parameter do not match with any of available trigger " + "mode of the camera ") + break + if not isinstance(trigger_mode, str): + self.log.debug("Trigger mode parameter must be a string ") + break + if not trigger_mode == self._trigger_mode: + self.log.info("Trigger mode parameter has not be changed ") + self._trigger_mode = trigger_mode + self.camera_device.set_trigger_mode(trigger_mode) + self.log.info("Trigger mode has been set correctly ") ############################################################################## # Shutter mode functions @@ -1312,116 +862,89 @@ def trigger_mode(self, trigger_mode): @property def shutter_mode(self): - """ - Getter method returning if the shutter is open. + """Getter method returning the shutter mode. - :return: @int shutter mode (0 permanently closed, 1 permanently open, 2 mode auto) or 4 if error + @return: (str) shutter mode (must be compared to the list) """ - shutter_open = self.camera_device.get_shutter_is_open() - is_int = isinstance(shutter_open, int) - if is_int: - self._shutter_mode = shutter_open - return shutter_open - else: - self.log.error("Your hardware method 'get_shutter_is_open()' is not returning an int ") - return 4 + self._shutter_mode = self.camera_device.get_shutter_is_open() + return self._shutter_mode @shutter_mode.setter def shutter_mode(self, shutter_mode): - """ - Setter method setting if the shutter is open. - - :param shutter_mode: @int shutter mode (0 permanently closed, 1 permanently open, 2 mode auto) - :return: nothing - """ - is_int = isinstance(shutter_mode, int) - is_change = shutter_mode == self._shutter_mode - if is_int and is_change: - self._shutter_mode = shutter_mode - self.camera_device.set_shutter_is_open(shutter_mode) - else: - if not is_int: - self.log.debug("Shutter open mode parameter must be an int ") - else: - self.log.info("Shutter mode parameter has not be changed ") + """Setter method setting the shutter mode. + + @param shutter_mode: (str) shutter mode (must be compared to the list) + @return: nothing + """ + if not shutter_mode in self._shutter_mode_list: + self.log.debug("Shutter mode parameter do not match with any of available shutter " + "mode of the camera ") + break + if not isinstance(shutter_mode, int): + self.log.debug("Shutter open mode parameter must be an int ") + break + if not shutter_mode == self._shutter_mode: + self.log.info("Shutter mode parameter has not be changed ") + break + self._shutter_mode = shutter_mode + self.camera_device.set_shutter_is_open(shutter_mode) + self.log.info("Shutter mod has been set correctly ") ############################################################################## # Temperature functions ############################################################################## @property - def cooler_ON(self): - """ - Getter method returning the cooler status if ON or OFF. + def cooler_status(self): + """Getter method returning the cooler status if ON or OFF. - :return: @bool True if ON or False if OFF or 0 if error + @return: (int) 1 if ON or 0 if OFF """ - cooler_ON = self.camera_device.get_cooler_ON() - is_bool = isinstance(cooler_ON, bool) - if is_bool: - self._cooler_ON = cooler_ON - return cooler_ON - else: - self.log.error("Your hardware method 'get_cooler_ON()' is not returning a boolean ") - return 0 + self._cooler_status = self.camera_device.get_cooler_status() + return self._cooler_status - @cooler_ON.setter - def cooler_ON(self, cooler_ON): - """ - Getter method returning the cooler status if ON or OFF. + @cooler_status.setter + def cooler_status(self, cooler_status): + """Getter method returning the cooler status if ON or OFF. - :cooler_ON: @bool True if ON or False if OFF - :return: nothing + @param cooler_status: (bool) 1 if ON or 0 if OFF + @return: nothing """ - is_bool = isinstance(cooler_ON, str) - is_change = cooler_ON == self._cooler_ON - if is_bool and is_change: - self._cooler_ON = cooler_ON - self.camera_device.set_cooler_ON(cooler_ON) - else: - if not is_bool: - self.log.debug("Cooler status parameter must be a boolean ") - else: - self.log.info("Cooler status parameter has not be changed ") + if not isinstance(cooler_status, int): + self.log.debug("Cooler status parameter must be int ") + break + if not cooler_status == self._cooler_status: + self.log.info("Cooler status parameter has not be changed ") + break + self._cooler_ON = cooler_ON + self.camera_device.set_cooler_ON(cooler_ON) + self.log.info("Cooler status has been changed correctly ") @property def camera_temperature(self): - """ - Getter method returning the temperature of the camera. + """Getter method returning the temperature of the camera. - :return: @float temperature or 0 if error + @return: (float) temperature """ - camera_temperature = self.camera_device.get_temperature() - is_float = isinstance(camera_temperature, float) - is_pos = camera_temperature > 0 - if is_float and is_pos: - self._camera_temperature = camera_temperature - return camera_temperature - else: - if not is_float: - self.log.error("Your hardware method 'get_temperature()' is not returning a float ") - else: - self.log.error("Your hardware method 'get_temperature()' is not returning a positive number ") - return 0 + self._camera_temperature = self.camera_device.get_temperature() + return self._camera_temperature @camera_temperature.setter def camera_temperature(self, camera_temperature): - """ - Getter method returning the temperature of the camera. - - :param temperature: @float temperature or 0 if error - :return: nothing - """ - is_float = isinstance(camera_temperature, float) - is_pos = camera_temperature > 0 - is_change = camera_temperature == self._camera_temperature - if is_float and is_pos and is_change: - self._camera_temperature = camera_temperature - self.camera_device.set_temperature(camera_temperature) - else: - if not is_float: - self.log.debug("Camera temperature parameter must be a float ") - if not is_pos: - self.log.debug("Camera temperature parameter must be a positive number ") - else: - self.log.info("Camera temperature parameter has not be changed ") \ No newline at end of file + """Getter method returning the temperature of the camera. + + @param temperature: (float) temperature + @return: nothing + """ + if not isinstance(camera_temperature, float): + self.log.debug("Camera temperature parameter must be a float ") + break + if not camera_temperature > 0: + self.log.debug("Camera temperature parameter must be a positive number ") + break + if not self._camera_temperature == camera_temperature: + self.log.info("Camera temperature parameter has not be changed ") + break + self._camera_temperature = camera_temperature + self.camera_device.set_temperature(camera_temperature) + self.log.info("Camera temperature has been changed correctly ") \ No newline at end of file From 05b53a697bf1dbf5d7bd3a569eb8c134e01ebc26 Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 17 Apr 2020 05:17:13 +0200 Subject: [PATCH 16/49] Updated Interfaces (Camera+Spectro) I updated functions and doctype related to what I did on the logic module --- hardware/spectrometer/shamrock.py | 60 ++++-- interface/camera_complete_interface.py | 213 ++++++++----------- interface/spectrometer_complete_interface.py | 193 +++++------------ 3 files changed, 182 insertions(+), 284 deletions(-) diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 5a17fa1e64..05ee3af6f9 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -58,6 +58,17 @@ class Shamrock(Base,SpectrometerInterface): SLIT_MIN_WIDTH=10E-6 SLIT_MAX_WIDTH=2500E-6 + def get_constraint(self): + number_of_gratings = self.get_number_gratings() + wavelength_limits = np.array([[self.get_wavelength_limit(i)] for i in range(number_of_gratings)]) + auto_slit_installed = np.array([[self.auto_slit_is_present('input',0), self.auto_slit_is_present('input',1)], + [self.auto_slit_is_present('output',0), self.auto_slit_is_present('output',1)]]) + flipper_mirror_installed = np.array([self.flipper_mirror_is_present('input'), self.flipper_mirror_is_present('output')]) + constraint_dict = {'number_of_gratings':number_of_gratings, + 'wavelength_limits':wavelength_limits, + 'auto_slit_installed':auto_slit_installed, + 'flipper_mirror_installed':flipper_mirror_installed} + ############################################################################## # Basic functions ############################################################################## @@ -357,28 +368,6 @@ def get_wavelength_limit(self, grating): else : self.log.error('get_wavelength_limit function "grating" parameter needs to be int type') - def get_calibration(self, number_pixels): - """ - Returns the wavelength calibration of each pixel (m) - @params int number_pixels - - Tested : yes - SI check : yes - - Important Note : ShamrockSetNumberPixels and ShamrockSetPixelWidth must have been called - otherwise this function will return -1 - """ - - if (number_pixels == self.get_number_of_pixels()): - wl_array = np.ones((number_pixels,), dtype=np.float32) - self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] - self.check(self.dll.ShamrockGetCalibration(self.deviceID, wl_array.ctypes.data, number_pixels)) - return wl_array*1E-9 - else: - self.log.debug("get_calibration : your argument seems not consistant with the current number of pixels\ - did you really perform shamrock.set_number_of_pixel before to proceed ?") - return -1 - def set_number_of_pixels(self, number_of_pixels): """ Sets the number of pixels of the detector (to prepare for calibration) @@ -439,6 +428,33 @@ def get_pixel_width(self): self.check(self.dll.ShamrockGetPixelWidth(self.deviceID, ct.byref(pixel_width))) return pixel_width.value*1E-6 +############################################################################## +# Calibration functions +############################################################################## + + def get_calibration(self): + """ + Returns the wavelength calibration of each pixel (m) + @params int number_pixels + + Tested : yes + SI check : yes + + Important Note : ShamrockSetNumberPixels and ShamrockSetPixelWidth must have been called + otherwise this function will return -1 + """ + number_pixels = self.get_number_of_pixels() + wl_array = np.ones((number_pixels,), dtype=np.float32) + self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] + self.check(self.dll.ShamrockGetCalibration(self.deviceID, wl_array.ctypes.data, number_pixels)) + return wl_array*1E-9 + + def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): + + self.set_number_of_pixels(number_of_pixels) + self.set_pixel_width(pixel_width) + self.set_detector_offset(tracks_offset) + ############################################################################## # Detector functions ############################################################################## diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 23e2925658..2ed7c88f6f 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -29,39 +29,29 @@ class CameraInterface(metaclass=InterfaceMetaclass): """ This interface is used to manage and visualize a simple camera """ - ############################################################################## - # Basic functions - ############################################################################## - @abstract_interface_method - def get_name(self): - """ Retrieve an identifier of the camera that the GUI can print + def get_constraint(self): + """Returns all the fixed parameters of the hardware which can be used by the logic. - @return string: name for the camera + @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], + 'acquistion_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], + 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], + 'shutter_mode_list' : ['CLOSE', 'OPEN'...] + 'image_size' : (512, 2048), + 'pixiel_size' : (1e-4, 1e-4), + 'name' : 'Newton940'} """ pass - @abstract_interface_method - def get_image_size(self): - """ Retrieve size of the image in pixel - - @return tuple: Size (width, height) - """ - pass - - @abstract_interface_method - def get_pixel_size(self): - """ Retrieve the pixel size (unit is meter) - - @return tuple: pixel_size (x,y) - """ - pass + ############################################################################## + # Basic functions + ############################################################################## @abstract_interface_method def start_acquisition(self): """ Start a single acquisition - @return bool: Success ? + @return: nothing """ pass @@ -69,7 +59,7 @@ def start_acquisition(self): def stop_acquisition(self): """ Stop/abort live or single acquisition - @return bool: Success ? + @return: nothing """ pass @@ -77,84 +67,69 @@ def stop_acquisition(self): def get_acquired_data(self): """ Return an array of last acquired image. - @return numpy array: image data in format [[row],[row]...] - + @return: (ndarray) image data in format [[row],[row]...] Each pixel might be a float, integer or sub pixels """ pass - - @abstract_interface_method - def get_ready_state(self): - """ Is the camera ready for an acquisition ? - - @return bool: ready ? - """ - pass - ############################################################################## # Read mode functions ############################################################################## @abstract_interface_method def get_read_mode(self): - """ - Getter method returning the current read mode used by the camera. + """Getter method returning the current read mode used by the camera. - :return: @str read mode (must be compared to a dict) + @return: (str) read mode """ pass @abstract_interface_method def set_read_mode(self, read_mode): - """ - Setter method setting the read mode used by the camera. + """Setter method setting the read mode used by the camera. - :param read_mode: @str read mode (must be compared to a dict) - :return: nothing + @param read_mode: (str) read mode + @return: nothing """ pass @abstract_interface_method - def get_track_parameters(self): - """ - Getter method returning the read mode tracks parameters of the camera. + def get_active_tracks(self): + """Getter method returning the read mode tracks parameters of the camera. - :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error + @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] """ pass @abstract_interface_method - def set_track_parameters(self, number_of_track, track_heigth, track_offset): + def set_active_tracks(self, active_tracks): """ Setter method setting the read mode tracks parameters of the camera. - :param number_of_track: @int number of track - :param track_heigth: @int track height - :param track_offset: @int track offset - :return: nothing + @param active_tracks: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @return: nothing """ pass @abstract_interface_method - def get_image_parameters(self): - """ - Getter method returning the read mode image parameters of the camera. + def get_active_image(self): + """Getter method returning the read mode image parameters of the camera. - :return: @tuple (@int pixel height, @int pixel width, @tuple (@int start raw, @int end raw), - @tuple (@int start column, @int end column)) or 0 if error + @return: (ndarray) active image parameters [hbin, vbin, hstart, hend, vstart, vend] """ pass @abstract_interface_method - def set_image_parameters(self, superpixel_size, superimage_size, superimage_position): - """ - Setter method setting the read mode image parameters of the camera. + def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): + """Setter method setting the read mode image parameters of the camera. - :param superpixel_size: @tuple (@int start raw, @int end raw) - :param superimage_size: @tuple (@int number of raw, @int number of column) - :param superimage_position: @tuple (@int bottom left corner raw, @int bottom left corner column) - :return: nothing + @param hbin: (int) horizontal pixel binning + @param vbin: (int) vertical pixel binning + @param hstart: (int) image starting row + @param hend: (int) image ending row + @param vstart: (int) image starting column + @param vend: (int) image ending column + @return: nothing """ pass @@ -167,60 +142,50 @@ def get_acquisition_mode(self): """ Getter method returning the current acquisition mode used by the camera. - :return: @str acquisition mode (must be compared to a dict) + @return: (str) acquisition mode """ pass @abstract_interface_method def set_acquisition_mode(self, acquisition_mode): - """ - Setter method setting the acquisition mode used by the camera. + """Setter method setting the acquisition mode used by the camera. - :param read_mode: @str read mode (must be compared to a dict) - :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes - :return: nothing + @param acquisition_mode: (str) acquistion mode + @return: nothing """ pass @abstract_interface_method def get_accumulation_delay(self): - """ - Getter method returning the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. + """Getter method returning the accumulation delay between consecutive scan during accumulate acquisition mode. - :return: @int accumulation cycle delay or 0 if error + @return: (float) accumulation delay """ pass @abstract_interface_method def set_accumulation_delay(self, accumulation_delay): - """ - Setter method setting the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. + """Setter method setting the accumulation delay between consecutive scan during an accumulate acquisition mode. - :param accumulation_time: @int accumulation cycle delay - :return: nothing + @param accumulation_delay: (float) accumulation delay + @return: nothing """ pass @abstract_interface_method def get_number_accumulated_scan(self): - """ - Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. + """Getter method returning the number of accumulated scan during accumulate acquisition mode. - :return: @int number of accumulated scan or 0 if error + @return: (int) number of accumulated scan """ pass @abstract_interface_method def set_number_accumulated_scan(self, number_scan): - """ - Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. + """Setter method setting the number of accumulated scan during accumulate acquisition mode. - :param number_scan: @int number of accumulated scan - :return: nothing + @param number_scan: (int) number of accumulated scan + @return: nothing """ pass @@ -228,35 +193,35 @@ def set_number_accumulated_scan(self, number_scan): def get_exposure_time(self): """ Get the exposure time in seconds - @return float exposure time + @return: (float) exposure time """ pass @abstract_interface_method def set_exposure_time(self, exposure_time): - """ Set the exposure time in seconds + """ Set the exposure time in seconds. - @param float time: desired new exposure time + @param exposure_time: (float) desired new exposure time - @return float: setted new exposure time + @return: nothing """ pass @abstract_interface_method def get_gain(self): - """ Get the gain + """ Get the gain. - @return float: exposure gain + @return: (float) exposure gain """ pass @abstract_interface_method def set_gain(self, gain): - """ Set the gain + """ Set the gain. - @param float gain: desired new gain + @param camera_gain: (float) desired new gain - @return float: new exposure gain + @return: nothing """ pass @@ -266,20 +231,18 @@ def set_gain(self, gain): @abstract_interface_method def get_trigger_mode(self): - """ - Getter method returning the current trigger mode used by the camera. + """Getter method returning the current trigger mode used by the camera. - :return: @str trigger mode (must be compared to a dict) + @return: (str) trigger mode (must be compared to the list) """ pass @abstract_interface_method def set_trigger_mode(self, trigger_mode): - """ - Setter method setting the trigger mode used by the camera. + """Setter method setting the trigger mode used by the camera. - :param trigger_mode: @str trigger mode (must be compared to a dict) - :return: nothing + @param trigger_mode: (str) trigger mode (must be compared to the list) + @return: nothing """ pass @@ -288,21 +251,19 @@ def set_trigger_mode(self, trigger_mode): ############################################################################## @abstract_interface_method - def shutter_is_open(self): - """ - Getter method returning if the shutter is open. + def shutter_mode(self): + """Getter method returning the shutter mode. - :return: @bool shutter open ? + @return: (str) shutter mode (must be compared to the list) """ pass @abstract_interface_method - def shutter_is_open(self, shutter_open): - """ - Setter method setting if the shutter is open. + def shutter_mode(self, shutter_mode): + """Setter method setting the shutter mode. - :param shutter_mode: @bool shutter open - :return: nothing + @param shutter_mode: (str) shutter mode (must be compared to the list) + @return: nothing """ pass @@ -311,40 +272,36 @@ def shutter_is_open(self, shutter_open): ############################################################################## @abstract_interface_method - def get_cooler_ON(self): - """ - Getter method returning the cooler status if ON or OFF. + def get_cooler_status(self): + """Getter method returning the cooler status if ON or OFF. - :return: @bool True if ON or False if OFF or 0 if error + @return: (int) 1 if ON or 0 if OFF """ pass @abstract_interface_method - def get_cooler_ON(self, cooler_ON): - """ - Getter method returning the cooler status if ON or OFF. + def set_cooler_status(self, cooler_status): + """Getter method returning the cooler status if ON or OFF. - :cooler_ON: @bool True if ON or False if OFF - :return: nothing + @param cooler_status: (bool) 1 if ON or 0 if OFF + @return: nothing """ pass @abstract_interface_method def get_temperature(self): - """ - Getter method returning the temperature of the camera. + """Getter method returning the temperature of the camera. - :return: @float temperature or 0 if error + @return: (float) temperature """ pass @abstract_interface_method def set_temperature(self, temperature): - """ - Getter method returning the temperature of the camera. + """Getter method returning the temperature of the camera. - :param temperature: @float temperature or 0 if error - :return: nothing + @param temperature: (float) temperature + @return: nothing """ pass \ No newline at end of file diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index a0793c4e73..51c03dea18 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -28,53 +28,51 @@ class SpectrometerInterface(metaclass=InterfaceMetaclass): This is the Interface class to define the controls for spectrometer hardware """ + @abstract_interface_method + def get_constraint(self): + """Returns all the fixed parameters of the hardware which can be used by the logic. + + @return: (dict) constraint dict : {'number_of_gratings' : 3, + 'wavelength_limits' : [[wavelength_min1, wavelength_max1], ... ], + 'auto_slit_installed' : [[front input slit, side input slit], [front output slit, side output slit]], + 'flipper_mirror_installed' : [input port, output port]} + """ + pass + ############################################################################## # Gratings functions ############################################################################## @abstract_interface_method def get_grating(self): - """ - Returns the current grating identification (0 to self.get_number_gratings-1) + """Returns the current grating identification (0 to self.get_number_gratings-1) """ pass @abstract_interface_method def set_grating(self, grating): - """ - Sets the required grating (0 to self.get_number_gratings-1) + """Sets the required grating (0 to self.get_number_gratings-1) - @param int grating: grating identification number + @param (int) grating: grating identification number @return: void """ pass - @abstract_interface_method - def get_number_gratings(self): - """ - Returns the number of gratings in the spectrometer - - @return int number_of_gratings - """ - pass - @abstract_interface_method def get_grating_offset(self, grating): - """ - Returns the grating offset (unit is motor steps) + """Returns the grating offset (unit is motor steps) - @param int grating (between 0 and number_of_gratings) - @return int grating offset (step) + @param (int) grating (between 0 and number_of_gratings) + @return (int) grating offset (step) """ pass @abstract_interface_method def set_grating_offset(self, grating, offset): - """ - Sets the grating offset (unit is motor step) + """Sets the grating offset (unit is motor step) - @param int grating : grating id (0..self.get_number_gratings() - int offset : grating offset (step) + @param (int) grating : grating id (0..self.get_number_gratings() + (int) offset : grating offset (step) """ pass @@ -84,90 +82,41 @@ def set_grating_offset(self, grating, offset): @abstract_interface_method def get_wavelength(self): - """ - Returns the central current wavelength (m) - @return float wavelength (m) - """ - pass - - @abstract_interface_method - def set_wavelength(self, wavelength): - """ - Sets the new central wavelength - @params float wavelength (m) - """ - - pass - - @abstract_interface_method - def get_wavelength_limit(self, grating): - """ - Returns the wavelength limits (m) of the grating (0-self.get_number_gratings) - @params int grating - """ - pass + """Returns the central current wavelength (m) - @abstract_interface_method - def get_calibration(self, number_pixels): - """ - Returns the wavelength calibration of each pixel (m) - @params int number_pixels - """ - pass - - @abstract_interface_method - def get_number_of_pixels(self): - """ - Returns the number of pixel that has to be previously set with self.set_number_of_pixels() - :return: int pixel number + @return (float) wavelength (m) """ pass @abstract_interface_method - def set_number_of_pixels(self, number_of_pixels): - """ - Sets the number of pixels of the detector (to prepare for calibration) - :param number_of_pixels: int - :return: nothing - """ - pass + def set_wavelength(self, wavelength): + """Sets the new central wavelength (m) - @abstract_interface_method - def get_pixel_width(self): + @params (float) wavelength (m) """ - Returns the pixel width along dispersion axis. - Note that pixel width has to be previously set with self.set_pixel_width(width) - :return: int pixel number - """ - pass - @abstract_interface_method - def set_pixel_width(self, width): - """ - Sets the pixel width along the dispersion axis (to prepare for calibration) - :param width: float unit is m - :return: nothing - """ pass ############################################################################## - # Detector functions + # Calibration functions ############################################################################## @abstract_interface_method - def get_detector_offset(self): - """ - Returns the detector offset in pixels - :return: int offset + def get_calibration(self): + """Returns the wavelength calibration of each pixel (m) + + @return: (ndarray) wavelength range for all the pixels of the camera """ pass @abstract_interface_method - def set_detector_offset(self, offset): - """ - Sets the detecotor offset in pixels - :param offset : int - :return: nothing + def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): + """Returns the wavelength calibration of each pixel (m). + + @param number_of_pixels: (int) number of pixels in the horizontal direction + @param pixel_width: (float) camera pixel width + @param tracks_offset: (int) camera pixel matrix offset + @return: nothing """ pass @@ -175,83 +124,59 @@ def set_detector_offset(self, offset): # Ports and Slits functions ############################################################################## - @abstract_interface_method - def flipper_mirror_is_present(self, flipper): - """ - Returns 1 if flipper mirror is present, 0 if not - - :param flipper: int 1 is for input, 2 is for output - :return: 1 or 0 - """ - - pass - @abstract_interface_method def get_input_port(self): - """ - Returns the current port for the input flipper mirror. - 0 is for front port, 1 is for side port + """Returns the current port for the input flipper mirror. + + @return: (int) 0 is for front port, 1 is for side port in case of no flipper mirror, front port (0) is used """ pass @abstract_interface_method def set_input_port(self, input_port): - """ - Sets the input port - 0 is for front port, 1 is for side port + """Sets the input port - 0 is for front port, 1 is for side port - :param input_port: int. has to be in [0, 1] - :return: nothing + @param input_port: (int). has to be 0 or 1 + @return: nothing """ pass @abstract_interface_method def get_output_port(self): - """ - Returns the current port for the output flipper mirror. - 0 is for front port, 1 is for side port + """Returns the current port for the output flipper mirror. + + @return: (int) 0 is for front port, 1 is for side port in case of no flipper mirror, front port (0) is used """ pass @abstract_interface_method def set_output_port(self, output_port): - """ - Sets the input port - 0 is for front port, 1 is for side port + """Sets the input port - 0 is for front port, 1 is for side port - :param input_port: int. has to be in [0, 1] - :return: nothing + @param output_port: (int). has to be 0 or 1 + @return: nothing """ pass @abstract_interface_method def get_auto_slit_width(self, flipper, port): - """ - Returns the input slit width (um) in case of a motorized slit, - :param string flipper - within ['input', 'output'] - int port - within[0,1] for front or side port - :return int offset - slit width, unit is meter (SI) + """Returns the input slit width (um) in case of a motorized slit. + + @param flipper: (str) within ['input', 'output'] + @param port: (int) 0 for front or 1 for side port + @return: (int) offset - slit width, unit is meter (SI) """ pass @abstract_interface_method def set_auto_slit_width(self, flipper, port, slit_width): - """ - Sets the new slit width for the required slit - :param flipper: string flipper - within ['input', 'output'] - :param port: int - within[0,1] for front or side port - :param slit_width: float - unit is meter (SI) - :return: nothing - """ - pass + """Sets the new slit width for the required slit. - @abstract_interface_method - def auto_slit_is_present(self, flipper, port): - """ - Return whether the required slit is present or not - :param flipper: string flipper - within ['input', 'output'] - :param port: int - within[0,1] for front or side port - :return: 1 if present, 0 if not + @param flipper: (str) within ['input', 'output'] + @param port: (int) 0 for front or 1 for side port + @param slit_width: (float) slit width unit is meter (SI) + :return: nothing """ pass - From e6e06846d9017b570438225e0c7a7f9147cb15e6 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Mon, 20 Apr 2020 10:20:04 +0200 Subject: [PATCH 17/49] HW / Newton940 update --- config/config_Hirondelle/hirondelle.cfg | 50 ++ hardware/camera/andor/Newton_940.py | 920 +++++++++----------- hardware/camera/andor/errorcodes_newton.h | 72 ++ hardware/spectrometer/errorcodes_shamrock.h | 40 + interface/camera_complete_interface.py | 214 ++--- 5 files changed, 654 insertions(+), 642 deletions(-) create mode 100644 config/config_Hirondelle/hirondelle.cfg create mode 100644 hardware/camera/andor/errorcodes_newton.h create mode 100644 hardware/spectrometer/errorcodes_shamrock.h diff --git a/config/config_Hirondelle/hirondelle.cfg b/config/config_Hirondelle/hirondelle.cfg new file mode 100644 index 0000000000..68fb2da81d --- /dev/null +++ b/config/config_Hirondelle/hirondelle.cfg @@ -0,0 +1,50 @@ +# Config file for a simple spectrometer +# +# +global: + # list of modules to load when starting + startup: ['man', 'tray'] # No idea + + module_server: + address: 'localhost' + port: 12345 + + ## For controlling the appearance of the GUI: ( Not Done yet) + stylesheet: 'qdark.qss' + +hardware: + shamrock: + module.Class: 'spectrometer.shamrock.Shamrock' + dll_location: 'C:\Users\uvspace\Documents\Hirondelle200\DLL\ShamrockCIF.dll' + + newton: + module.Class: 'camera.andor.Newton_940.Newton940' + dll_location: 'C:\Users\uvspace\Documents\Hirondelle200\DLL\atmcd64d.dll' + +logic: + spectrumlogic: + module.Class: 'spectrum_logic.SpectrumLogic' + connect: + spectrometer: 'shamrock' + camera: 'newton' + savelogic: 'savelogic' + + savelogic: + module.Class: 'save_logic.SaveLogic' + win_data_directory: 'C:/Data' # DO NOT CHANGE THE DIRECTORY HERE! ONLY IN THE CUSTOM FILE! + unix_data_directory: 'Data/' + log_into_daily_directory: True + save_pdf: True + save_png: True + +gui: + tray: + module.Class: 'trayicon.TrayIcon' + + man: + module.Class: 'manager.managergui.ManagerGui' + + spectrometer: + module.Class: 'PLspectrum.PL_spectrum_gui.PLspectrumGUI' + connect: + spectrumlogic: 'spectrumlogic' \ No newline at end of file diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 6d39e20a73..1fb0d26cbb 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -28,11 +28,14 @@ from ctypes import * import numpy as np import ctypes as ct +import ctypes from core.module import Base from core.configoption import ConfigOption from interface.camera_complete_interface import CameraInterface +from core.util.modules import get_main_dir +import os class ReadMode(Enum): @@ -42,6 +45,7 @@ class ReadMode(Enum): SINGLE_TRACK = 3 IMAGE = 4 + class AcquisitionMode(Enum): SINGLE_SCAN = 1 ACCUMULATE = 2 @@ -49,6 +53,7 @@ class AcquisitionMode(Enum): FAST_KINETICS = 4 RUN_TILL_ABORT = 5 + class TriggerMode(Enum): INTERNAL = 0 EXTERNAL = 1 @@ -57,6 +62,12 @@ class TriggerMode(Enum): SOFTWARE_TRIGGER = 10 EXTERNAL_CHARGE_SHIFTING = 12 +class ShutterMode(Enum): + AUTO = 0 + OPEN = 1 + CLOSE = 2 + + ERROR_DICT = { 20001: "DRV_ERROR_CODES", 20002: "DRV_SUCCESS", @@ -99,6 +110,7 @@ class TriggerMode(Enum): 20992: "DRV_NOT_AVAILABLE" } + class Newton940(Base, CameraInterface): """ Hardware class for Andors Newton940 @@ -108,37 +120,46 @@ class Newton940(Base, CameraInterface): _default_exposure = ConfigOption('default_exposure', 1.0) _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') - _default_temperature = ConfigOption('default_temperature', -70) - _default_cooler_on = ConfigOption('default_cooler_on', True) + _default_temperature = ConfigOption('default_temperature', -7) + _default_cooler_status = ConfigOption('default_cooler_status', True) _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') + _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') + _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) _dll_location = ConfigOption('dll_location', missing='error') - #_dll_location = 'ATMCD32D.dll' + # _dll_location = 'ATMCD32D.dll' _camera_name = 'Newton940' - - _exposure = _default_exposure _temperature = _default_temperature - _cooler_on = _default_cooler_on + _cooler_status = _default_cooler_status _read_mode = _default_read_mode _acquisition_mode = _default_acquisition_mode _gain = 0 _width = 0 _height = 0 _last_acquisition_mode = None # useful if config changes during acq - _supported_read_mode = ReadMode # TODO: read this from camera, all readmodes are available for iXon Ultra + _supported_read_mode = ReadMode # TODO: read this from camera, all readmodes are available for iXon Ultra _max_cooling = -85 _live = False _shutter = "closed" _trigger_mode = _default_trigger_mode - _scans = 1 #TODO get from camera + _scans = 1 # TODO get from camera _acquiring = False -################################################################################################### + _shutter_TTL = 1 + _shutter_closing_time = 100 #ms! + _shutter_opening_time = 100 #ms! + _shutter_status = _default_shutter_status + + _active_tracks = _default_active_tracks + _number_of_tracks = 1 + + + ############################################################################## # Basic module activation/deactivation -################################################################################################### +############################################################################## def on_activate(self): """ Initialization performed during activation of the module. @@ -153,27 +174,37 @@ def on_activate(self): self.log.info('Problem during camera (Andor/Newton) initialization') self.on_deactivate() else: - nx_px, ny_px = c_int(), c_int() - nx_px, ny_py = self._get_size() - self._width, self._height = nx_px.value, ny_px.value + nx_px, ny_px = c_int(), c_int() + nx_px, ny_px = self.get_image_size() + self._width, self._height = nx_px, ny_px + + self.set_read_mode(self._read_mode) + # à reprendre + + # self._set_trigger_mode(self._trigger_mode) + # self._set_exposuretime(self._exposure) - self._set_read_mode(self._read_mode) - self._set_trigger_mode(self._trigger_mode) - self._set_exposuretime(self._exposure) - self._set_acquisition_mode(self._acquisition_mode) + # ok + self.set_acquisition_mode(self._acquisition_mode) + self.set_cooler_status(self._cooler_status) + self.set_temperature(self._temperature) + + self.set_shutter_status(self._shutter_status) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ - self.stop_acquisition() - self._set_shutter(0, 0, 0.1, 0.1) - self._shut_down() + #self.stop_acquisition() + + # à reprendre + # self._set_shutter(0, 0, 0.1, 0.1) + self.dll.ShutDown() -################################################################################################### +############################################################################## # Error management -################################################################################################### +############################################################################## def check(self, func_val): """ Check routine for the received error codes. @@ -182,14 +213,13 @@ def check(self, func_val): """ if not func_val == 20002: - self.log.error('Error in Shamrock with errorcode {0}:\n' + self.log.error('Error in Newton with errorcode {0}:\n' '{1}'.format(func_val, self.errorcode[func_val])) return func_val def _create_errorcode(self): """ Create a dictionary with the errorcode for the device. """ - maindir = get_main_dir() filename = os.path.join(maindir, 'hardware', 'camera', 'andor', 'errorcodes_newton.h') @@ -198,67 +228,46 @@ def _create_errorcode(self): content = f.readlines() except: self.log.error('No file "errorcodes_newton.h" could be found in the ' - 'hardware/spectrometer directory!') + 'hardware/camera/andor/ directory!') + errorcode = {} for line in content: - if '#define NEWTON940' in line: + if '#define ' in line: errorstring, errorvalue = line.split()[-2:] errorcode[int(errorvalue)] = errorstring return errorcode -################################################################################################### +############################################################################## # Basic functions -################################################################################################### - - def get_name(self): - """ - :return: string local camera name with serial number - - """ - serial = ct.c_int() - self.check(self.dll.GetCameraSerialNumber(byref(serial))) - name = self._camera_name + " serial number " + str(serial.value) - return name - - def get_image_size(self): - """ - Returns the sensor size in pixels (x;y) - - :return: tuple (nw_px, ny_px) : int number of pixel along x and y axis - - Tested : no - SI check : ok - """ - nx_px = ct.c_int() - ny_px = ct.c_int() - self.check(self.dll.GetDetector(byref(nx_px), byref(ny_px))) - return nx_px.value, ny_px.value +############################################################################## - def get_pixel_size(self): - """ + def get_constraint(self): + """Returns all the fixed parameters of the hardware which can be used by the logic. - :return: + @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], + 'acquistion_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], + 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], + 'shutter_mode_list' : ['CLOSE', 'OPEN'...] + 'image_size' : (512, 2048), + 'pixiel_size' : (1e-4, 1e-4), + 'name' : 'Newton940'} """ - x_px = ct.c_float() - y_py = ct.c_float() - self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) - return x_px.value*1E-6, y_py.value*1E-6 + dico={} + dico['read_mode_list'] =['FVB','MULTI_TRACK', 'RANDOM_TRACK', 'SINGLE_TRACK', 'IMAGE'] + dico['acquistion_mode_list'] = ['SINGLE_SCAN', 'ACCUMULATE', 'KINETICS', 'FAST_KINETICS', 'RUN_TILL_ABORT'] + dico['trigger_mode_list'] = ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'] + dico['shutter_mode_list'] = ['AUTO', 'OPEN', 'CLOSE'] + dico['image_size'] = self.get_image_size() + dico['pixiel_size'] = self.get_pixel_size() + dico['name'] = self.get_name() - def get_ready_state(self): - code = ct.c_int() - self.check(self.dll.GetStatus(byref(code))) - if code.value==2073: - return True - else: - return False + return dico def start_acquisition(self): """ - Starts a single acquisition :return: nothing Tested : no - """ self.check(self.dll.StartAcquisition()) self.dll.WaitForAcquisition() @@ -273,593 +282,478 @@ def stop_acquisition(self): self.check(self.dll.AbortAcquisition()) return - def set_acquisition_mode(self, acquisition_mode, **kwargs): - """ - Setter method setting the acquisition mode used by the camera. + def get_acquired_data(self): + """ Return an array of last acquired image. - :param acquisition_mode: @str read mode (must be compared to a dict) - :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes - :return: nothing + @return numpy array: image data in format [[row],[row]...] + + Each pixel might be a float, integer or sub pixels """ + if self._acquisition_mode == 'SINGLE_SCAN': + if self._read_mode == 'FVB': + dim = self._width + h=1 - check_val = 0 - if hasattr(AcquisitionMode, mode): - n_mode = c_int(getattr(AcquisitionMode, mode).value) - error_code = self.dll.SetAcquisitionMode(n_mode) - else: - self.log.warning('{0} mode is not supported'.format(mode)) - check_val = -1 - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - check_val = -1 - else: - self._acquisition_mode = mode + if self._read_mode == 'RANDOM_TRACK': + dim = self._width*self._number_of_tracks + h=self._number_of_tracks + if self._read_mode == 'SINGLE_TRACK': + dim = self._width + h=1 + if self._read_mode == 'IMAGE': + dim = self._width*self._height + h=self._height - return check_val + dim = int(dim) + image_array = np.zeros(dim) + cimage_array = c_int * dim + cimage = cimage_array() -############################################################################## -# Read mode functions -############################################################################## + error_code = self.dll.GetAcquiredData(pointer(cimage), dim) + if ERROR_DICT[error_code] != 'DRV_SUCCESS': + self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) + else: + self.log.debug('image length {0}'.format(len(cimage))) + for i in range(len(cimage)): + # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode + image_array[i] = cimage[i] + image_array = np.reshape(image_array, (self._width, h)) + self._cur_image = image_array + return image_array + elif self.acquisition_mode == 'ACCUMULATE': + return +############################################################################## +# Read mode functions +############################################################################## +# is working, but not secured and SI + def get_read_mode(self): + """ + Getter method returning the current read mode used by the camera. + :return: @str read mode (must be compared to a dict) + The function GetReadMode does not exist in Andor SDK... surprising ! + We have to use a local variable. + """ + return self._read_mode + def set_read_mode(self, read_mode): + """ + Setter method setting the read mode used by the camera. + :param read_mode: @str read mode (must be compared to a dict) + :return: nothing + """ + if hasattr(ReadMode, read_mode): + n_mode = c_int(getattr(ReadMode, read_mode).value) + error_code = self.dll.SetReadMode(n_mode) + if read_mode == 'IMAGE': + self.log.debug("width:{0}, height:{1}".format(self._width, self._height)) + self.set_active_image(1, 1, 1, self._width, 1, self._height) + self._read_mode = read_mode + return - def support_live_acquisition(self): - """ Return whether or not the camera can take care of live acquisition + def get_active_tracks(self): + """Getter method returning the read mode tracks parameters of the camera. - @return bool: True if supported, False if not + @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] """ - return False + if self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'RANDOM_TRACK': + return self._active_tracks + else: + self.log.error('you are not in SINGLE_TRACK or RANDOM_TRACK read_mode') + return - def start_live_acquisition(self): - """ Start a continuous acquisition + def set_active_tracks(self, active_tracks): + """ + Setter method setting the read mode tracks parameters of the camera. - @return bool: Success ? + @param active_tracks: (numpy array of int32) active tracks positions [1st track start, 1st track end, ... ] + @return: nothing """ - if self._support_live: - self._live = True - self._acquiring = False - return False + number_of_tracks = int(len(active_tracks)/2) + self.dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] + if self._read_mode == 'FVB': + self.log.error('you want to define acquisition track, but current read_mode is FVB') + elif self._read_mode == 'MULTI_TRACK': + self.log.error('Please use RANDOM TRACK read mode for multi-track acquisition') + elif self._read_mode == 'IMAGE': + self.log.error('you want to define acquisition track, but current read_mode is IMAGE') + elif self._read_mode == 'SINGLE_TRACK' and number_of_tracks == 1: + self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) + elif self._read_mode == 'RANDOM_TRACK': + self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) + else: + self.log.error('problem with active tracks setting') + self._active_tracks=active_tracks + self._numbre_of_tracks=number_of_tracks + return + def get_active_image(self): + """Getter method returning the read mode image parameters of the camera. - def get_acquired_data(self): - """ Return an array of last acquired image. + @return: (ndarray) active image parameters [hbin, vbin, hstart, hend, vstart, vend] + """ + active_image_parameters = [self._hbin, self._vbin, self._hstart, self._hend, self._vstart, self._vend] + return active_image_parameters - @return numpy array: image data in format [[row],[row]...] + def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): + """Setter method setting the read mode image parameters of the camera. - Each pixel might be a float, integer or sub pixels + @param hbin: (int) horizontal pixel binning + @param vbin: (int) vertical pixel binning + @param hstart: (int) image starting row + @param hend: (int) image ending row + @param vstart: (int) image starting column + @param vend: (int) image ending column + @return: nothing """ + hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin), \ + c_int(hstart), c_int(hend), c_int(vstart), c_int(vend) - width = self._width - height = self._height - - if self._read_mode == 'IMAGE': - if self._acquisition_mode == 'SINGLE_SCAN': - dim = width * height - elif self._acquisition_mode == 'KINETICS': - dim = width * height * self._scans - elif self._acquisition_mode == 'RUN_TILL_ABORT': - dim = width * height - else: - self.log.error('Your acquisition mode is not covered currently') - elif self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'FVB': - if self._acquisition_mode == 'SINGLE_SCAN': - dim = width - elif self._acquisition_mode == 'KINETICS': - dim = width * self._scans + error_code = self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend) + msg = ERROR_DICT[error_code] + if msg == 'DRV_SUCCESS': + self._hbin = hbin.value + self._vbin = vbin.value + self._hstart = hstart.value + self._hend = hend.value + self._vstart = vstart.value + self._vend = vend.value + self._width = int((self._hend - self._hstart + 1) / self._hbin) + self._height = int((self._vend - self._vstart + 1) / self._vbin) else: - self.log.error('Your acquisition mode is not covered currently') + self.log.error('Call to SetImage went wrong:{0}'.format(msg)) + return - dim = int(dim) - image_array = np.zeros(dim) - cimage_array = c_int * dim - cimage = cimage_array() +############################################################################## +# Acquisition mode functions +############################################################################## - # this will be a bit hacky - if self._acquisition_mode == 'RUN_TILL_ABORT': - error_code = self.dll.GetOldestImage(pointer(cimage), dim) - else: - error_code = self.dll.GetAcquiredData(pointer(cimage), dim) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) + def get_acquisition_mode(self): + """ + Getter method returning the current acquisition mode used by the camera. + + :return: @str acquisition mode (must be compared to a dict) + """ + return self._acquisition_mode + + def set_acquisition_mode(self, acquisition_mode): + """ + Setter method setting the acquisition mode used by the camera. + + :param acquisition_mode: @str read mode (must be compared to a dict) + :return: nothing + """ + + if hasattr(AcquisitionMode, acquisition_mode): + n_mode = c_int(getattr(AcquisitionMode, acquisition_mode).value) + self.check(self.dll.SetAcquisitionMode(n_mode)) else: - self.log.debug('image length {0}'.format(len(cimage))) - for i in range(len(cimage)): - # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode - image_array[i] = cimage[i] + self.log.warning('{0} mode is not supported'.format(acquisition_mode)) - image_array = np.reshape(image_array, (self._width, self._height)) + self._acquisition_mode = acquisition_mode - self._cur_image = image_array - return image_array + return - def set_exposure(self, exposure): - """ Set the exposure time in seconds + def get_accumulation_delay(self): + """ + Getter method returning the accumulation cycle delay scan carry out during an accumulate acquisition mode + by the camera. - @param float time: desired new exposure time + :return: @int accumulation cycle delay or 0 if error + """ + pass - @return bool: Success? + def set_accumulation_delay(self, accumulation_delay): """ - msg = self._set_exposuretime(exposure) - if msg == "DRV_SUCCESS": - self._exposure = exposure - return True - else: - return False + Setter method setting the accumulation cycle delay scan carry out during an accumulate acquisition mode + by the camera. - def get_exposure(self): + :param accumulation_time: @int accumulation cycle delay + :return: nothing + """ + pass + + def get_number_accumulated_scan(self): + """ + Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :return: @int number of accumulated scan or 0 if error + """ + pass + + def set_number_accumulated_scan(self, number_scan): + """ + Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode + by the camera. + + :param number_scan: @int number of accumulated scan + :return: nothing + """ + pass + + def get_exposure_time(self): """ Get the exposure time in seconds @return float exposure time """ - self._get_acquisition_timings() + + exposure = c_float() + accumulate = c_float() + kinetic = c_float() + error_code = self.dll.GetAcquisitionTimings(byref(exposure), + byref(accumulate), + byref(kinetic)) + self._exposure = exposure.value + self._accumulate = accumulate.value + self._kinetic = kinetic.value + return self._exposure - # not sure if the distinguishing between gain setting and gain value will be problematic for - # this camera model. Just keeping it in mind for now. - #TODO: Not really funcitonal right now. - def set_gain(self, gain): - """ Set the gain + def set_exposure_time(self, exposure_time): + """ Set the exposure time in seconds - @param float gain: desired new gain + @param float time: desired new exposure time - @return float: new exposure gain + @return float: setted new exposure time """ - n_pre_amps = self._get_number_preamp_gains() - msg = '' - if (gain >= 0) & (gain < n_pre_amps): - msg = self._set_preamp_gain(gain) - else: - self.log.warning('Choose gain value between 0 and {0}'.format(n_pre_amps-1)) - if msg == 'DRV_SUCCESS': - self._gain = gain + # faire test sur type de exposure time + + # self.dll.SetExposureTime.argtypes = [ct.c_float] + + code = self.check(self.dll.SetExposureTime(c_float(exposure_time))) + + if code == 20002: + self._exposure = exposure_time + return True else: - self.log.warning('The gain wasn\'t set. {0}'.format(msg)) - return self._gain + self.log.error('Error during set_exposure_time') def get_gain(self): """ Get the gain @return float: exposure gain """ - _, self._gain = self._get_preamp_gain() - return self._gain + pass - def get_ready_state(self): - """ Is the camera ready for an acquisition ? + def set_gain(self, gain): + """ Set the gain - @return bool: ready ? - """ - status = c_int() - self._get_status(status) - if ERROR_DICT[status.value] == 'DRV_IDLE': - return True - else: - return False + @param float gain: desired new gain -# soon to be interface functions for using -# a camera as a part of a (slow) photon counter - def set_up_counter(self): - check_val = 0 - if self._shutter == 'closed': - msg = self._set_shutter(0, 1, 0.1, 0.1) - if msg == 'DRV_SUCCESS': - self._shutter = 'open' - else: - self.log.error('Problems with the shutter.') - check_val = -1 - ret_val1 = self._set_trigger_mode('EXTERNAL') - ret_val2 = self._set_acquisition_mode('RUN_TILL_ABORT') - # let's test the FT mode - # ret_val3 = self._set_frame_transfer(True) - error_code = self.dll.PrepareAcquisition() - error_msg = ERROR_DICT[error_code] - if error_msg == 'DRV_SUCCESS': - self.log.debug('prepared acquisition') - else: - self.log.debug('could not prepare acquisition: {0}'.format(error_msg)) - self._get_acquisition_timings() - if check_val == 0: - check_val = ret_val1 | ret_val2 + @return float: new exposure gain + """ + pass - if msg != 'DRV_SUCCESS': - ret_val3 = -1 - else: - ret_val3 = 0 +############################################################################## +# Trigger mode functions +############################################################################## - check_val = ret_val3 | check_val + def get_trigger_mode(self): + """ + Getter method returning the current trigger mode used by the camera. - return check_val + :return: @str trigger mode (must be compared to a dict) + """ + pass - def count_odmr(self, length): - first, last = self._get_number_new_images() - self.log.debug('number new images:{0}'.format((first, last))) - if last - first + 1 < length: - while last - first + 1 < length: - first, last = self._get_number_new_images() - else: - self.log.debug('acquired too many images:{0}'.format(last - first + 1)) + def set_trigger_mode(self, trigger_mode): + """ + Setter method setting the trigger mode used by the camera. - images = [] - for i in range(first, last + 1): - img = self._get_images(i, i, 1) - images.append(img) - self.log.debug('expected number of images:{0}'.format(length)) - self.log.debug('number of images acquired:{0}'.format(len(images))) - return np.array(images).transpose() + :param trigger_mode: @str trigger mode (must be compared to a dict) + :return: nothing + """ + pass - def get_down_time(self): - return self._exposure +############################################################################## +# Shutter mode functions +############################################################################## +# is working, but not secured and SI - def get_counter_channels(self): - width, height = self.get_size() - num_px = width * height - return [i for i in map(lambda x: 'px {0}'.format(x), range(num_px))] + def get_shutter_status(self): + """ + Getter method returning if the shutter is open. -# non interface functions regarding camera interface - def _abort_acquisition(self): - error_code = self.dll.AbortAcquisition() - return ERROR_DICT[error_code] + :return: @bool shutter open ? + """ + return self._shutter_status - def _shut_down(self): - error_code = self.dll.ShutDown() - return ERROR_DICT[error_code] + def set_shutter_status(self, shutter_status): + """ + Setter method setting if the shutter is open. - def _start_acquisition(self): - error_code = self.dll.StartAcquisition() - self.dll.WaitForAcquisition() - return ERROR_DICT[error_code] + :param shutter_mode: @bool shutter open + :return: nothing + """ -# setter functions + if hasattr(ShutterMode, shutter_status): + mode = c_int(getattr(ShutterMode, shutter_status).value) + self.dll.SetShutter(self._shutter_TTL, mode, self._shutter_closing_time, self._shutter_opening_time) + self._shutter_status = shutter_status - def _set_shutter(self, typ, mode, closingtime, openingtime): - """ - @param int typ: 0 Output TTL low signal to open shutter - 1 Output TTL high signal to open shutter - @param int mode: 0 Fully Auto - 1 Permanently Open - 2 Permanently Closed - 4 Open for FVB series - 5 Open for any series - """ - typ, mode, closingtime, openingtime = c_int(typ), c_int(mode), c_float(closingtime), c_float(openingtime) - error_code = self.dll.SetShutter(typ, mode, closingtime, openingtime) + return - return ERROR_DICT[error_code] +############################################################################## +# Temperature functions +############################################################################## +# is working, but not secured and SI - def _set_exposuretime(self, time): + def get_cooler_status(self): """ - @param float time: exposure duration - @return string answer from the camera - """ - error_code = self.dll.SetExposureTime(c_float(time)) - return ERROR_DICT[error_code] + Getter method returning the cooler status if ON or OFF. - def _set_read_mode(self, mode): + :return: @bool True if ON or False if OFF or 0 if error """ - @param string mode: string corresponding to certain ReadMode - @return string answer from the camera + return self._cooler_status + + def set_cooler_status(self, cooler_status): """ - check_val = 0 + Setter method returning the cooler status if ON or OFF. - if hasattr(ReadMode, mode): - n_mode = getattr(ReadMode, mode).value - n_mode = c_int(n_mode) - error_code = self.dll.SetReadMode(n_mode) - if mode == 'IMAGE': - self.log.debug("widt:{0}, height:{1}".format(self._width, self._height)) - msg = self._set_image(1, 1, 1, self._width, 1, self._height) - if msg != 'DRV_SUCCESS': - self.log.warning('{0}'.format(ERROR_DICT[error_code])) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Readmode was not set: {0}'.format(ERROR_DICT[error_code])) - check_val = -1 + :cooler_ON: @bool True if ON or False if OFF + :return: nothing + """ + if cooler_status: + self.check(self.dll.CoolerON()) + self._cooler_status=True else: - self._read_mode = mode - - return check_val + self.check(self.dll.CoolerOFF()) + self._cooler_status=False + return - def _set_trigger_mode(self, mode): + def get_temperature(self): """ - @param string mode: string corresponding to certain TriggerMode - @return string: answer from the camera + Getter method returning the temperature of the camera. + + :return: @float temperature (°C) or 0 if error """ - check_val = 0 - if hasattr(TriggerMode, mode): - n_mode = c_int(getattr(TriggerMode, mode).value) - self.log.debug('Input to function: {0}'.format(n_mode)) - error_code = self.dll.SetTriggerMode(n_mode) - else: - self.log.warning('{0} mode is not supported'.format(mode)) - check_val = -1 - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - check_val = -1 - else: - self._trigger_mode = mode + temp = c_int32() + self.dll.GetTemperature(byref(temp)) - return check_val + return temp.value - def _set_image(self, hbin, vbin, hstart, hend, vstart, vend): + def set_temperature(self, temperature): """ - This function will set the horizontal and vertical binning to be used when taking a full resolution image. - Parameters - @param int hbin: number of pixels to bin horizontally - @param int vbin: number of pixels to bin vertically. int hstart: Start column (inclusive) - @param int hend: End column (inclusive) - @param int vstart: Start row (inclusive) - @param int vend: End row (inclusive). + Getter method returning the temperature of the camera. - @return string containing the status message returned by the function call + :param temperature: @float temperature (°C) or 0 if error + :return: nothing """ - hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin),\ - c_int(hstart), c_int(hend), c_int(vstart), c_int(vend) + tempperature = c_int32(temperature) + self.dll.SetTemperature(temperature) + return - error_code = self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend) - msg = ERROR_DICT[error_code] - if msg == 'DRV_SUCCESS': - self._hbin = hbin.value - self._vbin = vbin.value - self._hstart = hstart.value - self._hend = hend.value - self._vstart = vstart.value - self._vend = vend.value - self._width = int((self._hend - self._hstart + 1) / self._hbin) - self._height = int((self._vend - self._vstart + 1) / self._vbin) - else: - self.log.error('Call to SetImage went wrong:{0}'.format(msg)) - return ERROR_DICT[error_code] +############################################################################## +# Internal functions, for constraints preparation +############################################################################## +# is working, but not secured and SI - def _set_output_amplifier(self, typ): + def get_name(self): """ - @param c_int typ: 0: EMCCD gain, 1: Conventional CCD register - @return string: error code + :return: string local camera name with serial number + """ - error_code = self.dll.SetOutputAmplifier(typ) - return ERROR_DICT[error_code] + serial = ct.c_int() + self.check(self.dll.GetCameraSerialNumber(byref(serial))) + name = self._camera_name + " serial number " + str(serial.value) + return name - def _set_preamp_gain(self, index): + def get_image_size(self): """ - @param c_int index: 0 - (Number of Preamp gains - 1) + Returns the sensor size in pixels (x;y) + + :return: tuple (nw_px, ny_px) : int number of pixel along x and y axis + + Tested : no + SI check : ok """ - error_code = self.dll.SetPreAmpGain(index) - return ERROR_DICT[error_code] + nx_px = ct.c_int() + ny_px = ct.c_int() + self.check(self.dll.GetDetector(byref(nx_px), byref(ny_px))) + return nx_px.value, ny_px.value - def _set_temperature(self, temp): - temp = c_int(temp) - error_code = self.dll.SetTemperature(temp) - return ERROR_DICT[error_code] + def get_pixel_size(self): + """ + :return: + """ + x_px = ct.c_float() + y_px = ct.c_float() + self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) + return x_px.value * 1E-6, y_px.value * 1E-6 - def _set_acquisition_mode(self, mode): + def get_ready_state(self): """ - Function to set the acquisition mode - @param mode: - @return: + + :return: """ - check_val = 0 - if hasattr(AcquisitionMode, mode): - n_mode = c_int(getattr(AcquisitionMode, mode).value) - error_code = self.dll.SetAcquisitionMode(n_mode) - else: - self.log.warning('{0} mode is not supported'.format(mode)) - check_val = -1 - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - check_val = -1 + code = ct.c_int() + self.check(self.dll.GetStatus(byref(code))) + if code.value == 20073: + return True else: - self._acquisition_mode = mode + return False + + + + + + + + - return check_val - def _set_cooler(self, state): - if state: - error_code = self.dll.CoolerON() - else: - error_code = self.dll.CoolerOFF() - return ERROR_DICT[error_code] - def _set_frame_transfer(self, bool): - acq_mode = self._acquisition_mode - if (acq_mode == 'SINGLE_SCAN') | (acq_mode == 'KINETIC'): - self.log.debug('Setting of frame transfer mode has no effect in acquisition ' - 'mode \'SINGLE_SCAN\' or \'KINETIC\'.') - return -1 - else: - if bool: - rtrn_val = self.dll.SetFrameTransferMode(1) - else: - rtrn_val = self.dll.SetFrameTransferMode(0) - if ERROR_DICT[rtrn_val] == 'DRV_SUCCESS': - return 0 - else: - self.log.warning('Could not set frame transfer mode:{0}'.format(ERROR_DICT[rtrn_val])) - return -1 -# getter functions - def _get_status(self, status): - error_code = self.dll.GetStatus(byref(status)) - return ERROR_DICT[error_code] - def _get_camera_serialnumber(self, number): - """ - Gives serial number - Parameters - """ - error_code = self.dll.GetCameraSerialNumber(byref(number)) - return ERROR_DICT[error_code] - def _get_acquisition_timings(self): - exposure = c_float() - accumulate = c_float() - kinetic = c_float() - error_code = self.dll.GetAcquisitionTimings(byref(exposure), - byref(accumulate), - byref(kinetic)) - self._exposure = exposure.value - self._accumulate = accumulate.value - self._kinetic = kinetic.value - return ERROR_DICT[error_code] - def _get_oldest_image(self): - """ Return an array of last acquired image. - @return numpy array: image data in format [[row],[row]...] - Each pixel might be a float, integer or sub pixels - """ - width = self._width - height = self._height - - if self._read_mode == 'IMAGE': - if self._acquisition_mode == 'SINGLE_SCAN': - dim = width * height / self._hbin / self._vbin - elif self._acquisition_mode == 'KINETICS': - dim = width * height / self._hbin / self._vbin * self._scans - elif self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'FVB': - if self._acquisition_mode == 'SINGLE_SCAN': - dim = width - elif self._acquisition_mode == 'KINETICS': - dim = width * self._scans - - dim = int(dim) - image_array = np.zeros(dim) - cimage_array = c_int * dim - cimage = cimage_array() - error_code = self.dll.GetOldestImage(pointer(cimage), dim) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Couldn\'t retrieve an image') - else: - self.log.debug('image length {0}'.format(len(cimage))) - for i in range(len(cimage)): - # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode - image_array[i] = cimage[i] - image_array = np.reshape(image_array, (int(self._width/self._hbin), int(self._height/self._vbin))) - return image_array - def _get_number_amp(self): - """ - @return int: Number of amplifiers available - """ - n_amps = c_int() - self.dll.GetNumberAmp(byref(n_amps)) - return n_amps.value - def _get_number_preamp_gains(self): - """ - Number of gain settings available for the pre amplifier - @return int: Number of gains available - """ - n_gains = c_int() - self.dll.GetNumberPreAmpGains(byref(n_gains)) - return n_gains.value - def _get_preamp_gain(self): - """ - Function returning - @return tuple (int1, int2): First int describing the gain setting, second value the actual gain - """ - index = c_int() - gain = c_float() - self.dll.GetPreAmpGain(index, byref(gain)) - return index.value, gain.value - def _get_temperature(self): - temp = c_int() - error_code = self.dll.GetTemperature(byref(temp)) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.error('Can not retrieve temperature'.format(ERROR_DICT[error_code])) - return temp.value - def _get_temperature_f(self): - """ - Status of the cooling process + current temperature - @return: (float, str) containing current temperature and state of the cooling process - """ - temp = c_float() - error_code = self.dll.GetTemperatureF(byref(temp)) - return temp.value, ERROR_DICT[error_code] - def _get_size_of_circular_ring_buffer(self): - index = c_long() - error_code = self.dll.GetSizeOfCircularBuffer(byref(index)) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.error('Can not retrieve size of circular ring ' - 'buffer: {0}'.format(ERROR_DICT[error_code])) - return index.value - def _get_number_new_images(self): - first = c_long() - last = c_long() - error_code = self.dll.GetNumberNewImages(byref(first), byref(last)) - msg = ERROR_DICT[error_code] - pass_returns = ['DRV_SUCCESS', 'DRV_NO_NEW_DATA'] - if msg not in pass_returns: - self.log.error('Can not retrieve number of new images {0}'.format(ERROR_DICT[error_code])) - return first.value, last.value - # not working properly (only for n_scans = 1) - def _get_images(self, first_img, last_img, n_scans): - """ Return an array of last acquired image. - @return numpy array: image data in format [[row],[row]...] - Each pixel might be a float, integer or sub pixels - """ - width = self._width - height = self._height - # first_img, last_img = self._get_number_new_images() - # n_scans = last_img - first_img - dim = width * height * n_scans - dim = int(dim) - image_array = np.zeros(dim) - cimage_array = c_int * dim - cimage = cimage_array() - first_img = c_long(first_img) - last_img = c_long(last_img) - size = c_ulong(width * height) - val_first = c_long() - val_last = c_long() - error_code = self.dll.GetImages(first_img, last_img, pointer(cimage), - size, byref(val_first), byref(val_last)) - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) - else: - for i in range(len(cimage)): - # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode - image_array[i] = cimage[i] - self._cur_image = image_array - return image_array -# non interface functions regarding setpoint interface diff --git a/hardware/camera/andor/errorcodes_newton.h b/hardware/camera/andor/errorcodes_newton.h new file mode 100644 index 0000000000..d6325cfd63 --- /dev/null +++ b/hardware/camera/andor/errorcodes_newton.h @@ -0,0 +1,72 @@ +/* + +This file contains the header file for the error code of the Andor-Shamrock device. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at + +*/ + + +/* +Taken from: + +*/ + + +#define DRV_ERROR_CODES 20001 +#define DRV_SUCCESS 20002 +#define DRV_VXNOTINSTALLED 20003 +#define DRV_ERROR_FILELOAD 20006 +#define DRV_ERROR_VXD_INIT 20007 +#define DRV_ERROR_PAGELOCK 20010 +#define DRV_ERROR_PAGE_UNLOCK 20011 +#define DRV_ERROR_ACK 20013 +#define DRV_NO_NEW_DATA 20024 +#define DRV_SPOOLERROR 20026 +#define DRV_TEMP_OFF 20034 +#define DRV_TEMP_NOT_STABILIZED 20035 +#define DRV_TEMP_STABILIZED 20036 +#define DRV_TEMP_NOT_REACHED 20037 +#define DRV_TEMP_OUT_RANGE 20038 +#define DRV_TEMP_NOT_SUPPORTED 20039 +#define DRV_TEMP_DRIFT 20040 +#define DRV_COF_NOTLOADED 20050 +#define DRV_FLEXERROR 20053 +#define DRV_P1INVALID 20066 +#define DRV_P2INVALID 20067 +#define DRV_P3INVALID 20068 +#define DRV_P4INVALID 20069 +#define DRV_INIERROR 20070 +#define DRV_COERROR 20071 +#define DRV_ACQUIRING 20072 +#define DRV_IDLE 20073 +#define DRV_TEMPCYCLE 20074 +#define DRV_NOT_INITIALIZED 20075 +#define DRV_P5INVALID 20076 +#define DRV_P6INVALID 20077 +#define P7_INVALID 20083 +#define DRV_USBERROR 20089 +#define DRV_NOT_SUPPORTED 20091 +#define DRV_INVALID_TRIGGER_MODE 20095 +#define DRV_BINNING_ERROR 20099 +#define DRV_NOCAMERA 20990 +#define DRV_NOT_SUPPORTED 20991 +#define DRV_NOT_AVAILABLE 20992 + + + + diff --git a/hardware/spectrometer/errorcodes_shamrock.h b/hardware/spectrometer/errorcodes_shamrock.h new file mode 100644 index 0000000000..8f835c302d --- /dev/null +++ b/hardware/spectrometer/errorcodes_shamrock.h @@ -0,0 +1,40 @@ +/* + +This file contains the header file for the error code of the Andor-Shamrock device. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at + +*/ + + +/* +Taken from: + +*/ + + +#define SHAMROCK_COMMUNICATION_ERROR 20201 +#define SHAMROCK_SUCCESS 20202 +#define SHAMROCK_P1INVALID 20266 +#define SHAMROCK_P2INVALID 20267 +#define SHAMROCK_P3INVALID 20268 +#define SHAMROCK_P4INVALID 20269 +#define SHAMROCK_P5INVALID 20270 +#define SHAMROCK_NOT_INITIALIZED 20275 +#define SHAMROCK_NOT_AVAILABLE 20292 + + diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 23e2925658..fc6c056acd 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -29,39 +29,29 @@ class CameraInterface(metaclass=InterfaceMetaclass): """ This interface is used to manage and visualize a simple camera """ - ############################################################################## - # Basic functions - ############################################################################## - @abstract_interface_method - def get_name(self): - """ Retrieve an identifier of the camera that the GUI can print + def get_constraint(self): + """Returns all the fixed parameters of the hardware which can be used by the logic. - @return string: name for the camera + @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], + 'acquistion_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], + 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], + 'shutter_mode_list' : ['CLOSE', 'OPEN'...] + 'image_size' : (512, 2048), + 'pixiel_size' : (1e-4, 1e-4), + 'name' : 'Newton940'} """ pass - @abstract_interface_method - def get_image_size(self): - """ Retrieve size of the image in pixel - - @return tuple: Size (width, height) - """ - pass - - @abstract_interface_method - def get_pixel_size(self): - """ Retrieve the pixel size (unit is meter) - - @return tuple: pixel_size (x,y) - """ - pass + ############################################################################## + # Basic functions + ############################################################################## @abstract_interface_method def start_acquisition(self): """ Start a single acquisition - @return bool: Success ? + @return: nothing """ pass @@ -69,7 +59,7 @@ def start_acquisition(self): def stop_acquisition(self): """ Stop/abort live or single acquisition - @return bool: Success ? + @return: nothing """ pass @@ -77,84 +67,69 @@ def stop_acquisition(self): def get_acquired_data(self): """ Return an array of last acquired image. - @return numpy array: image data in format [[row],[row]...] - + @return: (ndarray) image data in format [[row],[row]...] Each pixel might be a float, integer or sub pixels """ pass - - @abstract_interface_method - def get_ready_state(self): - """ Is the camera ready for an acquisition ? - - @return bool: ready ? - """ - pass - ############################################################################## # Read mode functions ############################################################################## @abstract_interface_method def get_read_mode(self): - """ - Getter method returning the current read mode used by the camera. + """Getter method returning the current read mode used by the camera. - :return: @str read mode (must be compared to a dict) + @return: (str) read mode """ pass @abstract_interface_method def set_read_mode(self, read_mode): - """ - Setter method setting the read mode used by the camera. + """Setter method setting the read mode used by the camera. - :param read_mode: @str read mode (must be compared to a dict) - :return: nothing + @param read_mode: (str) read mode + @return: nothing """ pass @abstract_interface_method - def get_track_parameters(self): - """ - Getter method returning the read mode tracks parameters of the camera. + def get_active_tracks(self): + """Getter method returning the read mode tracks parameters of the camera. - :return: @tuple (@int number of track, @int track height, @int track offset) or 0 if error + @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] """ pass @abstract_interface_method - def set_track_parameters(self, number_of_track, track_heigth, track_offset): + def set_active_tracks(self, active_tracks): """ Setter method setting the read mode tracks parameters of the camera. - :param number_of_track: @int number of track - :param track_heigth: @int track height - :param track_offset: @int track offset - :return: nothing + @param active_tracks: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @return: nothing """ pass @abstract_interface_method - def get_image_parameters(self): - """ - Getter method returning the read mode image parameters of the camera. + def get_active_image(self): + """Getter method returning the read mode image parameters of the camera. - :return: @tuple (@int pixel height, @int pixel width, @tuple (@int start raw, @int end raw), - @tuple (@int start column, @int end column)) or 0 if error + @return: (ndarray) active image parameters [hbin, vbin, hstart, hend, vstart, vend] """ pass @abstract_interface_method - def set_image_parameters(self, superpixel_size, superimage_size, superimage_position): - """ - Setter method setting the read mode image parameters of the camera. + def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): + """Setter method setting the read mode image parameters of the camera. - :param superpixel_size: @tuple (@int start raw, @int end raw) - :param superimage_size: @tuple (@int number of raw, @int number of column) - :param superimage_position: @tuple (@int bottom left corner raw, @int bottom left corner column) - :return: nothing + @param hbin: (int) horizontal pixel binning + @param vbin: (int) vertical pixel binning + @param hstart: (int) image starting row + @param hend: (int) image ending row + @param vstart: (int) image starting column + @param vend: (int) image ending column + @return: nothing """ pass @@ -167,60 +142,50 @@ def get_acquisition_mode(self): """ Getter method returning the current acquisition mode used by the camera. - :return: @str acquisition mode (must be compared to a dict) + @return: (str) acquisition mode """ pass @abstract_interface_method def set_acquisition_mode(self, acquisition_mode): - """ - Setter method setting the acquisition mode used by the camera. + """Setter method setting the acquisition mode used by the camera. - :param read_mode: @str read mode (must be compared to a dict) - :param kwargs: packed @dict which contain a series of arguments specific to the differents acquisition modes - :return: nothing + @param acquisition_mode: (str) acquistion mode + @return: nothing """ pass @abstract_interface_method def get_accumulation_delay(self): - """ - Getter method returning the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. + """Getter method returning the accumulation delay between consecutive scan during accumulate acquisition mode. - :return: @int accumulation cycle delay or 0 if error + @return: (float) accumulation delay """ pass @abstract_interface_method def set_accumulation_delay(self, accumulation_delay): - """ - Setter method setting the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. + """Setter method setting the accumulation delay between consecutive scan during an accumulate acquisition mode. - :param accumulation_time: @int accumulation cycle delay - :return: nothing + @param accumulation_delay: (float) accumulation delay + @return: nothing """ pass @abstract_interface_method def get_number_accumulated_scan(self): - """ - Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. + """Getter method returning the number of accumulated scan during accumulate acquisition mode. - :return: @int number of accumulated scan or 0 if error + @return: (int) number of accumulated scan """ pass @abstract_interface_method def set_number_accumulated_scan(self, number_scan): - """ - Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. + """Setter method setting the number of accumulated scan during accumulate acquisition mode. - :param number_scan: @int number of accumulated scan - :return: nothing + @param number_scan: (int) number of accumulated scan + @return: nothing """ pass @@ -228,35 +193,35 @@ def set_number_accumulated_scan(self, number_scan): def get_exposure_time(self): """ Get the exposure time in seconds - @return float exposure time + @return: (float) exposure time """ pass @abstract_interface_method def set_exposure_time(self, exposure_time): - """ Set the exposure time in seconds + """ Set the exposure time in seconds. - @param float time: desired new exposure time + @param exposure_time: (float) desired new exposure time - @return float: setted new exposure time + @return: nothing """ pass @abstract_interface_method def get_gain(self): - """ Get the gain + """ Get the gain. - @return float: exposure gain + @return: (float) exposure gain """ pass @abstract_interface_method def set_gain(self, gain): - """ Set the gain + """ Set the gain. - @param float gain: desired new gain + @param camera_gain: (float) desired new gain - @return float: new exposure gain + @return: nothing """ pass @@ -266,20 +231,18 @@ def set_gain(self, gain): @abstract_interface_method def get_trigger_mode(self): - """ - Getter method returning the current trigger mode used by the camera. + """Getter method returning the current trigger mode used by the camera. - :return: @str trigger mode (must be compared to a dict) + @return: (str) trigger mode (must be compared to the list) """ pass @abstract_interface_method def set_trigger_mode(self, trigger_mode): - """ - Setter method setting the trigger mode used by the camera. + """Setter method setting the trigger mode used by the camera. - :param trigger_mode: @str trigger mode (must be compared to a dict) - :return: nothing + @param trigger_mode: (str) trigger mode (must be compared to the list) + @return: nothing """ pass @@ -288,21 +251,19 @@ def set_trigger_mode(self, trigger_mode): ############################################################################## @abstract_interface_method - def shutter_is_open(self): - """ - Getter method returning if the shutter is open. + def get_shutter_status(self): + """Getter method returning the shutter mode. - :return: @bool shutter open ? + @return: (str) shutter mode (must be compared to the list) """ pass @abstract_interface_method - def shutter_is_open(self, shutter_open): - """ - Setter method setting if the shutter is open. + def set_shutter_status(self, shutter_mode): + """Setter method setting the shutter mode. - :param shutter_mode: @bool shutter open - :return: nothing + @param shutter_mode: (str) shutter mode (must be compared to the list) + @return: nothing """ pass @@ -311,40 +272,35 @@ def shutter_is_open(self, shutter_open): ############################################################################## @abstract_interface_method - def get_cooler_ON(self): - """ - Getter method returning the cooler status if ON or OFF. + def get_cooler_status(self): + """Getter method returning the cooler status if ON or OFF. - :return: @bool True if ON or False if OFF or 0 if error + @return: (int) 1 if ON or 0 if OFF """ pass @abstract_interface_method - def get_cooler_ON(self, cooler_ON): - """ - Getter method returning the cooler status if ON or OFF. + def set_cooler_status(self, cooler_status): + """Getter method returning the cooler status if ON or OFF. - :cooler_ON: @bool True if ON or False if OFF - :return: nothing + @param cooler_status: (bool) 1 if ON or 0 if OFF + @return: nothing """ pass - @abstract_interface_method def get_temperature(self): - """ - Getter method returning the temperature of the camera. + """Getter method returning the temperature of the camera. - :return: @float temperature or 0 if error + @return: (float) temperature """ pass @abstract_interface_method def set_temperature(self, temperature): - """ - Getter method returning the temperature of the camera. + """Getter method returning the temperature of the camera. - :param temperature: @float temperature or 0 if error - :return: nothing + @param temperature: (float) temperature + @return: nothing """ pass \ No newline at end of file From da7c7b5d14eddbaaf377ec44012f8b5a1dd632e9 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Mon, 20 Apr 2020 17:00:29 +0200 Subject: [PATCH 18/49] Hardware Camera and interface update advanced version of camera hardware and interface (missing SI units checking, code checking ..) --- hardware/camera/andor/Newton_940.py | 166 +++++++++++++------------ interface/camera_complete_interface.py | 41 ++---- 2 files changed, 93 insertions(+), 114 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 1fb0d26cbb..4c28fa86fb 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -45,7 +45,6 @@ class ReadMode(Enum): SINGLE_TRACK = 3 IMAGE = 4 - class AcquisitionMode(Enum): SINGLE_SCAN = 1 ACCUMULATE = 2 @@ -53,7 +52,6 @@ class AcquisitionMode(Enum): FAST_KINETICS = 4 RUN_TILL_ABORT = 5 - class TriggerMode(Enum): INTERNAL = 0 EXTERNAL = 1 @@ -67,6 +65,17 @@ class ShutterMode(Enum): OPEN = 1 CLOSE = 2 +GAIN_DICT = { + 0: 1, # index=0 - gain is 1x + 1: 2, # index=1 - gain is 2x + 2: 4 # ... +} + +READOUT_SPEED_DICT = { + 0: 50000, # index=0 - Horizontal shift is 50kHz + 1: 1000000, # index=1 - Horizontal shift is 1MHz + 2: 3000000 # ... +} ERROR_DICT = { 20001: "DRV_ERROR_CODES", @@ -113,53 +122,58 @@ class ShutterMode(Enum): class Newton940(Base, CameraInterface): """ - Hardware class for Andors Newton940 + Hardware class for Andor Newton940 CCD spectroscopy cameras """ _modtype = 'camera' _modclass = 'hardware' - _default_exposure = ConfigOption('default_exposure', 1.0) - _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') - _default_temperature = ConfigOption('default_temperature', -7) _default_cooler_status = ConfigOption('default_cooler_status', True) + _default_temperature = ConfigOption('default_temperature', -7) _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') + _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') + _default_readout_speed = ConfigOption('default_readout_speed', 50000) + _default_preamp_gain = ConfigOption('default_preamp_gain', 1) _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') + _default_exposure = ConfigOption('default_exposure', 1.0) _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) _dll_location = ConfigOption('dll_location', missing='error') - # _dll_location = 'ATMCD32D.dll' _camera_name = 'Newton940' - _exposure = _default_exposure - _temperature = _default_temperature _cooler_status = _default_cooler_status - _read_mode = _default_read_mode + _temperature = _default_temperature + _max_cooling = -85 _acquisition_mode = _default_acquisition_mode + _read_mode = _default_read_mode + _readout_speed = _default_readout_speed + _preamp_gain = _default_preamp_gain + _trigger_mode = _default_trigger_mode + + _exposure = _default_exposure + _shutter_status = _default_shutter_status + _shutter_TTL = 1 + _shutter_closing_time = 100 #ms! + _shutter_opening_time = 100 #ms! + _gain = 0 _width = 0 _height = 0 _last_acquisition_mode = None # useful if config changes during acq - _supported_read_mode = ReadMode # TODO: read this from camera, all readmodes are available for iXon Ultra - _max_cooling = -85 + _supported_read_mode = ReadMode # _live = False - _shutter = "closed" - _trigger_mode = _default_trigger_mode - _scans = 1 # TODO get from camera - _acquiring = False - _shutter_TTL = 1 - _shutter_closing_time = 100 #ms! - _shutter_opening_time = 100 #ms! - _shutter_status = _default_shutter_status + _scans = 1 + _acquiring = False _active_tracks = _default_active_tracks _number_of_tracks = 1 - - ############################################################################## +############################################################################## # Basic module activation/deactivation ############################################################################## +# is working, but not secured and SI + def on_activate(self): """ Initialization performed during activation of the module. @@ -173,22 +187,23 @@ def on_activate(self): if code != 20002: self.log.info('Problem during camera (Andor/Newton) initialization') self.on_deactivate() + else: - nx_px, ny_px = c_int(), c_int() + #nx_px, ny_px = c_int(), c_int() nx_px, ny_px = self.get_image_size() self._width, self._height = nx_px, ny_px - self.set_read_mode(self._read_mode) - # à reprendre - - # self._set_trigger_mode(self._trigger_mode) - # self._set_exposuretime(self._exposure) - - # ok - self.set_acquisition_mode(self._acquisition_mode) self.set_cooler_status(self._cooler_status) self.set_temperature(self._temperature) + self.set_acquisition_mode(self._acquisition_mode) + self.set_read_mode(self._read_mode) + self.set_readout_speed(self._readout_speed) + self.set_gain(self._preamp_gain) + self.set_trigger_mode(self._trigger_mode) + + self.set_exposure_time(self._exposure) + self.set_shutter_status(self._shutter_status) def on_deactivate(self): @@ -205,7 +220,7 @@ def on_deactivate(self): ############################################################################## # Error management ############################################################################## - +# is working def check(self, func_val): """ Check routine for the received error codes. :return: the dll function error code @@ -254,13 +269,14 @@ def get_constraint(self): 'name' : 'Newton940'} """ dico={} - dico['read_mode_list'] =['FVB','MULTI_TRACK', 'RANDOM_TRACK', 'SINGLE_TRACK', 'IMAGE'] - dico['acquistion_mode_list'] = ['SINGLE_SCAN', 'ACCUMULATE', 'KINETICS', 'FAST_KINETICS', 'RUN_TILL_ABORT'] + dico['read_mode_list'] =['FVB', 'RANDOM_TRACK', 'SINGLE_TRACK', 'IMAGE'] + dico['acquistion_mode_list'] = ['SINGLE_SCAN'] dico['trigger_mode_list'] = ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'] dico['shutter_mode_list'] = ['AUTO', 'OPEN', 'CLOSE'] dico['image_size'] = self.get_image_size() dico['pixiel_size'] = self.get_pixel_size() dico['name'] = self.get_name() + dico['Pream Gain'] = [1, 2, 4] return dico @@ -298,9 +314,11 @@ def get_acquired_data(self): if self._read_mode == 'RANDOM_TRACK': dim = self._width*self._number_of_tracks h=self._number_of_tracks + if self._read_mode == 'SINGLE_TRACK': dim = self._width h=1 + if self._read_mode == 'IMAGE': dim = self._width*self._height h=self._height @@ -330,10 +348,6 @@ def get_acquired_data(self): - elif self.acquisition_mode == 'ACCUMULATE': - return - - ############################################################################## # Read mode functions @@ -369,6 +383,19 @@ def set_read_mode(self, read_mode): return + def get_readout_speed(self): + return self._readout_speed + + def set_readout_speed(self, readout_speed): + if readout_speed in list(READOUT_SPEED_DICT.values()): + readout_speed_index=list(READOUT_SPEED_DICT.values()).index(readout_speed) + self.check(self.dll.SetHSSpeed(0,readout_speed_index)) + self._readout_speed = readout_speed + return + else: + self.log.warning('Hardware / Newton940 / set.readout_speed : readout_speed value is not available') + + def get_active_tracks(self): """Getter method returning the read mode tracks parameters of the camera. @@ -405,7 +432,7 @@ def set_active_tracks(self, active_tracks): self.log.error('problem with active tracks setting') self._active_tracks=active_tracks - self._numbre_of_tracks=number_of_tracks + self._number_of_tracks=number_of_tracks return @@ -449,6 +476,7 @@ def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): ############################################################################## # Acquisition mode functions ############################################################################## +# is working, but not secured and SI def get_acquisition_mode(self): """ @@ -476,44 +504,6 @@ def set_acquisition_mode(self, acquisition_mode): return - def get_accumulation_delay(self): - """ - Getter method returning the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. - - :return: @int accumulation cycle delay or 0 if error - """ - pass - - def set_accumulation_delay(self, accumulation_delay): - """ - Setter method setting the accumulation cycle delay scan carry out during an accumulate acquisition mode - by the camera. - - :param accumulation_time: @int accumulation cycle delay - :return: nothing - """ - pass - - def get_number_accumulated_scan(self): - """ - Getter method returning the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. - - :return: @int number of accumulated scan or 0 if error - """ - pass - - def set_number_accumulated_scan(self, number_scan): - """ - Setter method setting the number of accumulated scan carry out during an accumulate acquisition mode - by the camera. - - :param number_scan: @int number of accumulated scan - :return: nothing - """ - pass - def get_exposure_time(self): """ Get the exposure time in seconds @@ -556,7 +546,7 @@ def get_gain(self): @return float: exposure gain """ - pass + return self._preamp_gain def set_gain(self, gain): """ Set the gain @@ -565,11 +555,19 @@ def set_gain(self, gain): @return float: new exposure gain """ - pass + + if gain in list(GAIN_DICT.values()): + gain_index=list(GAIN_DICT.values()).index(gain) + self.check(self.dll.SetPreAmpGain(gain_index)) + self._preamp_gain = gain + return + else: + self.log.warning('Hardware / Newton940 / set.gain : gain value is not available') ############################################################################## # Trigger mode functions ############################################################################## +# is working, but not secured and SI def get_trigger_mode(self): """ @@ -577,7 +575,7 @@ def get_trigger_mode(self): :return: @str trigger mode (must be compared to a dict) """ - pass + return self._trigger_mode def set_trigger_mode(self, trigger_mode): """ @@ -586,7 +584,13 @@ def set_trigger_mode(self, trigger_mode): :param trigger_mode: @str trigger mode (must be compared to a dict) :return: nothing """ - pass + if hasattr(TriggerMode, trigger_mode): + n_mode = c_int(getattr(TriggerMode, trigger_mode).value) + self.check(self.dll.SetTriggerMode(n_mode)) + self._trigger_mode = trigger_mode + else: + self.log.warning('{0} mode is not supported'.format(trigger_mode)) + return ############################################################################## # Shutter mode functions diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index fc6c056acd..0315421fa3 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -93,6 +93,14 @@ def set_read_mode(self, read_mode): """ pass + @abstract_interface_method + def get_readout_speed(self): + pass + + @abstract_interface_method + def set_readout_speed(self, readout_speed): + pass + @abstract_interface_method def get_active_tracks(self): """Getter method returning the read mode tracks parameters of the camera. @@ -155,39 +163,6 @@ def set_acquisition_mode(self, acquisition_mode): """ pass - @abstract_interface_method - def get_accumulation_delay(self): - """Getter method returning the accumulation delay between consecutive scan during accumulate acquisition mode. - - @return: (float) accumulation delay - """ - pass - - @abstract_interface_method - def set_accumulation_delay(self, accumulation_delay): - """Setter method setting the accumulation delay between consecutive scan during an accumulate acquisition mode. - - @param accumulation_delay: (float) accumulation delay - @return: nothing - """ - pass - - @abstract_interface_method - def get_number_accumulated_scan(self): - """Getter method returning the number of accumulated scan during accumulate acquisition mode. - - @return: (int) number of accumulated scan - """ - pass - - @abstract_interface_method - def set_number_accumulated_scan(self, number_scan): - """Setter method setting the number of accumulated scan during accumulate acquisition mode. - - @param number_scan: (int) number of accumulated scan - @return: nothing - """ - pass @abstract_interface_method def get_exposure_time(self): From 2a3f5e09f1de52a4eaabe5f740cf3a65b7166796 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Wed, 22 Apr 2020 15:41:02 +0200 Subject: [PATCH 19/49] Added review for spectrum_logic --- logic/spectrum_logic.py | 106 +++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 6a65f10916..50bc8896b2 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -41,7 +41,7 @@ class SpectrumLogic(GenericLogic): # declare connectors spectrometer = Connector(interface='SpectrometerInterface') camera = Connector(interface='CameraInterface') - savelogic = Connector(interface='SaveLogic', optional=True) + savelogic = Connector(interface='SaveLogic') # declare status variables (logic attribute) : _spectrum_data = StatusVar('spectrum_data', np.empty((2, 0))) @@ -49,16 +49,16 @@ class SpectrumLogic(GenericLogic): _image_data = StatusVar('image_data', np.empty((2, 0))) # Allow to set a default save directory in the config file : - _default_save_file_path = ConfigOption('default_save_file_path') - _save_file_path = StatusVar('save_file_path', _default_save_file_path) + _default_save_file_path = ConfigOption('default_save_file_path') #TODO: Qudi savelogic handle the saving, it's better to let it do it things + _save_file_path = StatusVar('save_file_path', _default_save_file_path) # TODO: same # declare status variables (camera attribute) : _read_mode = StatusVar('read_mode', 'FVB') - _active_tracks = StatusVar('active_tracks', [240, 240]) + _active_tracks = StatusVar('active_tracks', [240, 240]) # TODO: some camera have only one pixel height, or not support anything else than FVB _acquistion_mode = StatusVar('acquistion_mode', 'MULTI_SCAN') - _exposure_time = StatusVar('exposure_time', 1e-4) - _camera_gain = StatusVar('camera_gain', 1) + _exposure_time = StatusVar('exposure_time', 1) + _camera_gain = StatusVar('camera_gain', 1) # TODO: even if unlikely, some camera might not have 1 in its possible value _number_of_scan = StatusVar('number_of_scan', 1) _scan_delay = StatusVar('scan_delay', 1e-2) _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) @@ -76,19 +76,18 @@ def __init__(self, **kwargs): @param dict kwargs: optional parameters """ super().__init__(**kwargs) - - # locking for thread safety - self.threadlock = Mutex() + self.threadlock = Mutex() # TODO: This line on its own does nothing def on_activate(self): """ Initialisation performed during activation of the module. """ - - self.spectrometer_device = self.spectrometer() + self.spectrometer_device = self.spectrometer() #TODO: New modules prefer the syntax self.spectrometer() directly in the code rather than storing a second reference in a variable self.camera_device = self.camera() self._save_logic = self.savelogic() # hardware constraints : + #TODO: You don't need to copy every entry to the module attributes, you can just use self.constraints['auto_slit_installed'] + # You can merge the two dictionaries or keep them separate spectro_constraints = self.spectrometer_device.get_constraints() self._number_of_gratings = spectro_constraints['number_of_gratings'] self._wavelength_limits = spectro_constraints['wavelength_limits'] @@ -104,11 +103,16 @@ def on_activate(self): self._pixel_size = camera_constraints['pixel_size'] # Spectrometer calibration using camera contraints parameters: - self.spectrometer_device.set_calibration(self._image_size[1], self._pixel_size[1], self._image_size[0]/2) + self.spectrometer_device.set_calibration(self._image_size[1], self._pixel_size[1], self._image_size[0]/2) #TODO # declare spectrometer attributes : # grating : + # TODO: Here you can initialize the hidden variable with : + # self._attribute = self.spectrometer().get_attribute() + # and then in the logic getter just return : self._attribute + # Here is way it is initialize works but it quite unusual self._grating = self.grating + # self._grating = self.spectrometer().get_grating() #todo and then in the grating property return self._grating directly self._grating_offset = self.grating_offset #wavelenght : @@ -121,13 +125,13 @@ def on_activate(self): self._output_slit_width = self.output_slit_width # declare camera attributes : - self._active_image = self._image_size + self._active_image = self._image_size #TODO This line surprise me from the variable name self._shutter_mode = self.shutter_mode self._cooler_status = self.cooler_status self._camera_temperature = self.camera_temperature - # QTimer for asynchrone execution : + # QTimer for asynchronous execution : self._timer = QtCore.QTimer() self._timer.timeout.connect(self.acquire_data) self._counter = 0 @@ -135,7 +139,7 @@ def on_activate(self): def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ - if self.module_state() != 'idle' and self.module_state() != 'deactivated': + if self.module_state() != 'idle' and self.module_state() != 'deactivated': #TODO: if the module is not idle, this function needs to stop the acquisition, it can not disobey ! pass ############################################################################## @@ -153,20 +157,20 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): data = OrderedDict() today = date.today() if data_type == 'image': - data = self._image_data[:, :] + data = self._image_data[:, :] # TODO: tab[:, :] is equivalent to just tab if data_type == 'spectrum': - data['Wavelength (m)'] = self._spectrum_data[0, :] - data['PL intensity (u.a)'] = self._spectrum_data[1, :] + data['Wavelength (m)'] = self._spectrum_data[0, :] #TODO: It's better to use simple variable-like name for this. This will be clear the day the data are treated ! + data['PL intensity (u.a)'] = self._spectrum_data[1, :] #TODO: just "counts" would be more appropriate, it's not (u.a.) elif data_type == 'background': data['Wavelength (m)'] = self._background_data[0, :] data['PL intensity (u.a)'] = self._background_data[1, :] else: - self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' + self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' #TODO: this should be an error ' \'background\' or \'image\'') if file_name is not None: file_label = file_name else: - file_label = '{}_'.format(data_type) + today.strftime("%d%m%Y") + file_label = '{}_'.format(data_type) + today.strftime("%d%m%Y") #TODO: Savelogic already does timestamping if save_path is not None: self._save_file_path = save_path self._save_logic.save_data(data, @@ -178,24 +182,24 @@ def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): # Acquisition functions ############################################################################## - def start_spectrum_acquisition(self): - """Start acquisition by lauching the timer signal calling the 'acquire_data' function. + def start_spectrum_acquisition(self): #TODO: In the code this function also starts image + """ Start acquisition by lauching the timer signal calling the 'acquire_data' function. """ self._counter = 0 - self.module_state.lock() - self._timer.start(1000 * self._scan_delay) #The argument of QTimer.start() is in ms + self.module_state.lock() #TODO: this method should check if the module is already soemthing + self._timer.start(1000 * self._scan_delay) #The argument of QTimer.start() is in ms #TODO: Why is the acquisition not started right away ? What exactly is _scan_delay ? def acquire_data(self): - """Method acquiring data by using the camera hardware mathode 'start_acquistion'. This method is connected + """ Method acquiring data by using the camera hardware methode 'start_acquistion'. This method is connected to a timer signal : after timer start this slot is called with a period of a time delay. After a certain number of call this method can stop the timer if not in 'LIVE' acquisition. """ - self.shutter_mode('OPEN') + self.shutter_mode('OPEN') #TODO: Some hardware do not have a shutter self.camera_device.start_acquisition() - self.shutter_mode('CLOSE') + self.shutter_mode('CLOSE') # TODO: start_acquisition only starts it, this line is executed while it is still ongoing self._counter += 1 if self._read_mode == 'IMAGE': - self._image_data = np.concatenate(self._image_data, self.acquired_data) + self._image_data = np.concatenate(self._image_data, self.acquired_data) #TODO: I don't understand what this tries to do name = 'Image' else: self._spectrum_data = np.concatenate(self._spectrum_data, self.acquired_data) @@ -254,7 +258,7 @@ def image_data(self): ############################################################################## @property - def grating(self): + def grating(self): #TODO: From the name, it's unclear if it's the grating id or an object describing the grating that is returned """Getter method returning the grating number used by the spectrometer. @return: (int) active grating number @@ -272,13 +276,16 @@ def grating(self, grating_number): """ if not isinstance(grating_number, int): self.log.debug('Grating parameter is not correct : it must be an integer ') + # TODO: a break is to end a loop in the middle of it, python will raise an error on this line + # If you want to stop the function use return break if not 0 < grating_number < self._number_of_gratings: self.log.debug('Grating parameter is not correct : it must be in range 0 to {} ' .format(self._number_of_gratings - 1)) break - if not grating_number != self._grating: - self.log.info('Grating parameter has not been changed') + if not grating_number != self._grating: #TODO: This will only generate a lot of lines in the log file, it's common for the GUI to update an attribute to its current value + #TODO: And if the value has not changed, they we should not invoke the hardware setter + self.log.info('Grating parameter has not been changed') # break self.spectrometer_device.set_grating(grating_number) self._grating = grating_number @@ -290,7 +297,7 @@ def grating_offset(self): @return: (int) the corresponding grating offset """ - self._grating_offset = self.spectrometer_device.get_grating_offset(self._grating) + self._grating_offset = self.spectrometer_device.get_grating_offset(self._grating) #TODO: I though we decided this to be handled in logic only return self._grating_offset @grating_offset.setter @@ -304,7 +311,7 @@ def grating_offset(self, grating_offset): if not isinstance(grating_offset, int): self.log.debug('Offset parameter is not correct : it must be an integer ') break - offset_min = -self._number_of_gratings//2 - self._number_of_gratings % 2 + offset_min = -self._number_of_gratings//2 - self._number_of_gratings % 2 #TODO: I don't understand what is the idea here offset_max = self._number_of_gratings//2 if not offset_min < grating_offset < offset_max: self.log.debug('Offset parameter is not correct : it must be in range {} to {} ' @@ -337,11 +344,15 @@ def center_wavelength(self, wavelength): @param wavelength: (float) center wavelength @return: nothing """ - if not isinstance(wavelength, float): + if not isinstance(wavelength, float): #TODO: This test is not essential and can generate problem in can the parameter is a int + # If you want to be sure the wavelength is a float, you can do : wavelength = float(wavelength) + # This way python tries to cast the type and raise an error if it's not possible + # This is true for a lot of type testing in this module self.log.debug('Wavelength parameter is not correct : it must be a float ') break wavelength_min = self._wavelength_limits[self._grating, 0] - wavelength_max = self._wavelength_limits[self._grating, 1] + wavelength_max = self._wavelength_limits[self._grating, 1] # TODO: You can write : + #wavelength_min, wavelength_max = self._wavelength_limits[self._grating] # python will try to unpack automatically if not wavelength_min < wavelength < wavelength_max: self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' .format(wavelength_min, wavelength_max)) @@ -354,20 +365,20 @@ def center_wavelength(self, wavelength): self.log.info('Spectrometer wavelength has been changed correctly ') @property - def wavelength_range(self): + def wavelength_range(self): #TODO: The name of the property is confusing """Getter method returning the wavelength array of the full measured spectral range. (used for plotting spectrum with the spectral range) @return: (ndarray) measured wavelength array """ - self._wavelength_range = self.spectrometer_device.get_calibration() + self._wavelength_range = self.spectrometer_device.get_calibration() #TODO return self._wavelength_range ############################################################################## # Calibration functions ############################################################################## - def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): + def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): # TODO: This function will be erased in the future """Setter method returning the detector offset used by the spectrometer DLLs calibration function. (the value returned by this function must be the real detector offset value of the camera) @@ -420,6 +431,8 @@ def input_port(self, input_port): @param input_port: (int) active input port (0 front and 1 side) @return: nothing """ + #TODO: All this test could be replaced by : + # if input_port not in self.constraints.input_ports: if input_port==1 and not self._flipper_mirror_installed[0]: self.log.debug('Your hardware do not have any flipper mirror present at the input port ') break @@ -540,7 +553,7 @@ def output_slit_width(self, slit_width): @property def acquired_data(self): - """ Return an array of last acquired image. + """ Return an array of last acquired image. #TODO: This function also works for spectra @return: (ndarray) image data in format [[row],[row]...] Each pixel might be a float, integer or sub pixels @@ -572,7 +585,7 @@ def read_mode(self, read_mode): self.log.debug("Read mode parameter do not match with any of the available read " "mode of the camera ") break - if not isinstance(read_mode, str): + if not isinstance(read_mode, str): #TODO: This test will never fail after the previous one self.log.debug("Read mode parameter must be a string ") break if not read_mode == self._read_mode: @@ -597,10 +610,11 @@ def active_tracks(self, active_tracks): @param active_tracks: (ndarray) active tracks positions [1st track start, 1st track end, ... ] @return: nothing """ + # TODO: this function will change when the tracks become of the format [(10, 20), (55, 57), ...] if not (np.all(active_tracks[::2] 0: self.log.debug("Exposure time parameter must be a positive number ") break - if not exposure_time < self._accumulation_delay: + if not exposure_time < self._accumulation_delay: #TODO: This is confusing self.log.debug("Exposure time parameter must be a value lower" "that the current accumulation time values ") break @@ -892,7 +906,7 @@ def shutter_mode(self, shutter_mode): def cooler_status(self): """Getter method returning the cooler status if ON or OFF. - @return: (int) 1 if ON or 0 if OFF + @return: (int) 1 if ON or 0 if OFF #TODO: why not use 'ON' or 'OFF' """ self._cooler_status = self.camera_device.get_cooler_status() return self._cooler_status @@ -910,8 +924,8 @@ def cooler_status(self, cooler_status): if not cooler_status == self._cooler_status: self.log.info("Cooler status parameter has not be changed ") break - self._cooler_ON = cooler_ON - self.camera_device.set_cooler_ON(cooler_ON) + self._cooler_ON = cooler_status + self.camera_device.set_cooler_ON(cooler_status) self.log.info("Cooler status has been changed correctly ") @property @@ -924,7 +938,7 @@ def camera_temperature(self): return self._camera_temperature @camera_temperature.setter - def camera_temperature(self, camera_temperature): + def camera_temperature(self, camera_temperature): #TODO: this set the setpoint, not the temperature """Setter method returning the temperature of the camera. @param temperature: (float) temperature From 7e705fd5d7bb0d4448a8d5fa32c777a9df2abb41 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Wed, 22 Apr 2020 16:23:14 +0200 Subject: [PATCH 20/49] Update camera hardware ready for review --- hardware/camera/andor/Newton_940.py | 627 ++++++++++++++++------------ 1 file changed, 368 insertions(+), 259 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 4c28fa86fb..4a1da2a513 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -28,7 +28,6 @@ from ctypes import * import numpy as np import ctypes as ct -import ctypes from core.module import Base from core.configoption import ConfigOption @@ -39,19 +38,29 @@ class ReadMode(Enum): + # We leave here all the possible read modes, but SINGLE TRACK and MULTI TRACK are considered + # as particular cases of RANDOM TRACK. They are not included in the constraint dictionary + # and could be removed here. + FVB = 0 MULTI_TRACK = 1 RANDOM_TRACK = 2 SINGLE_TRACK = 3 IMAGE = 4 + class AcquisitionMode(Enum): + # We leave here all the possible acquisition modes, but we considere that ACCUMULATE and KINETICS + # have to be done in the logic stage. FAST_KINETICS and RUN_TILL_ABORT are not implemented and + # therefore, they are not included in the constraint dictionary + SINGLE_SCAN = 1 ACCUMULATE = 2 KINETICS = 3 FAST_KINETICS = 4 RUN_TILL_ABORT = 5 + class TriggerMode(Enum): INTERNAL = 0 EXTERNAL = 1 @@ -60,34 +69,36 @@ class TriggerMode(Enum): SOFTWARE_TRIGGER = 10 EXTERNAL_CHARGE_SHIFTING = 12 + class ShutterMode(Enum): AUTO = 0 OPEN = 1 CLOSE = 2 + GAIN_DICT = { - 0: 1, # index=0 - gain is 1x - 1: 2, # index=1 - gain is 2x - 2: 4 # ... + 0: 1, # index=0 - gain is 1x + 1: 2, # index=1 - gain is 2x + 2: 4 # ... } READOUT_SPEED_DICT = { - 0: 50000, # index=0 - Horizontal shift is 50kHz - 1: 1000000, # index=1 - Horizontal shift is 1MHz + 0: 50000, # index=0 - Horizontal shift is 50kHz + 1: 1000000, # index=1 - Horizontal shift is 1MHz 2: 3000000 # ... } ERROR_DICT = { 20001: "DRV_ERROR_CODES", 20002: "DRV_SUCCESS", - 20003: "DRV_VXNOTINSTALLED", - 20006: "DRV_ERROR_FILELOAD", + 20003: "DRV_VX_NOT_INSTALLED", + 20006: "DRV_ERROR_FILE_LOAD", 20007: "DRV_ERROR_VXD_INIT", - 20010: "DRV_ERROR_PAGELOCK", + 20010: "DRV_ERROR_PAGE_LOCK", 20011: "DRV_ERROR_PAGE_UNLOCK", 20013: "DRV_ERROR_ACK", 20024: "DRV_NO_NEW_DATA", - 20026: "DRV_SPOOLERROR", + 20026: "DRV_SPOOL_ERROR", 20034: "DRV_TEMP_OFF", 20035: "DRV_TEMP_NOT_STABILIZED", 20036: "DRV_TEMP_STABILIZED", @@ -95,26 +106,26 @@ class ShutterMode(Enum): 20038: "DRV_TEMP_OUT_RANGE", 20039: "DRV_TEMP_NOT_SUPPORTED", 20040: "DRV_TEMP_DRIFT", - 20050: "DRV_COF_NOTLOADED", - 20053: "DRV_FLEXERROR", + 20050: "DRV_COF_NOT_LOADED", + 20053: "DRV_FLEX_ERROR", 20066: "DRV_P1INVALID", 20067: "DRV_P2INVALID", 20068: "DRV_P3INVALID", 20069: "DRV_P4INVALID", - 20070: "DRV_INIERROR", - 20071: "DRV_COERROR", + 20070: "DRV_INI_ERROR", + 20071: "DRV_CO_ERROR", 20072: "DRV_ACQUIRING", 20073: "DRV_IDLE", - 20074: "DRV_TEMPCYCLE", + 20074: "DRV_TEMP_CYCLE", 20075: "DRV_NOT_INITIALIZED", 20076: "DRV_P5INVALID", 20077: "DRV_P6INVALID", 20083: "P7_INVALID", - 20089: "DRV_USBERROR", + 20089: "DRV_USB_ERROR", 20091: "DRV_NOT_SUPPORTED", 20095: "DRV_INVALID_TRIGGER_MODE", 20099: "DRV_BINNING_ERROR", - 20990: "DRV_NOCAMERA", + 20990: "DRV_NO_CAMERA", 20991: "DRV_NOT_SUPPORTED", 20992: "DRV_NOT_AVAILABLE" } @@ -128,7 +139,7 @@ class Newton940(Base, CameraInterface): _modclass = 'hardware' _default_cooler_status = ConfigOption('default_cooler_status', True) - _default_temperature = ConfigOption('default_temperature', -7) + _default_temperature = ConfigOption('default_temperature', 260) _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') _default_readout_speed = ConfigOption('default_readout_speed', 50000) @@ -137,6 +148,10 @@ class Newton940(Base, CameraInterface): _default_exposure = ConfigOption('default_exposure', 1.0) _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) + _default_binning = ConfigOption('default_binning', [1, 1]) + _default_ROI = ConfigOption('default_ROI', [1, 2048, 1, 512]) + _default_max_exposure_time = ConfigOption('default_max_exposure_time', 600) + _dll_location = ConfigOption('dll_location', missing='error') _camera_name = 'Newton940' @@ -151,76 +166,97 @@ class Newton940(Base, CameraInterface): _trigger_mode = _default_trigger_mode _exposure = _default_exposure + _max_exposure_time = _default_max_exposure_time _shutter_status = _default_shutter_status _shutter_TTL = 1 - _shutter_closing_time = 100 #ms! - _shutter_opening_time = 100 #ms! + _shutter_closing_time = 100 # ms! + _shutter_opening_time = 100 # ms! _gain = 0 _width = 0 _height = 0 - _last_acquisition_mode = None # useful if config changes during acq - _supported_read_mode = ReadMode # + _supported_read_mode = ReadMode _live = False _scans = 1 _acquiring = False - _active_tracks = _default_active_tracks _number_of_tracks = 1 - -############################################################################## -# Basic module activation/deactivation -############################################################################## -# is working, but not secured and SI + _binning = _default_binning + _ROI = _default_ROI + + _hbin = 1 + _vbin = 1 + _hstart = 1 + _hend = 2 + _vstart = 1 + _vend = 2 + + _constraints = {} + _min_temperature = 189 + _max_temperature = 262 + + ############################################################################## + # Basic module activation/deactivation + ############################################################################## + # is working + # secured OK - tested PV - SI OK def on_activate(self): """ Initialization performed during activation of the module. - """ self.dll = ct.cdll.LoadLibrary(self._dll_location) - self.errorcode = self._create_errorcode() + self.error_code = self._create_error_code() - code = self.dll.Initialize() + error_code = self.dll.Initialize() - if code != 20002: + if ERROR_DICT[error_code] != 'DRV_SUCCESS': self.log.info('Problem during camera (Andor/Newton) initialization') self.on_deactivate() else: - #nx_px, ny_px = c_int(), c_int() + self._constraints = self.get_constraint() nx_px, ny_px = self.get_image_size() self._width, self._height = nx_px, ny_px - self.set_cooler_status(self._cooler_status) - self.set_temperature(self._temperature) + self.set_cooler_status(self._cooler_status) + self.set_temperature(self._temperature) - self.set_acquisition_mode(self._acquisition_mode) - self.set_read_mode(self._read_mode) - self.set_readout_speed(self._readout_speed) - self.set_gain(self._preamp_gain) - self.set_trigger_mode(self._trigger_mode) + self.set_acquisition_mode(self._acquisition_mode) + self.set_read_mode(self._read_mode) + self.set_readout_speed(self._readout_speed) + self.set_gain(self._preamp_gain) + self.set_trigger_mode(self._trigger_mode) - self.set_exposure_time(self._exposure) + self.set_exposure_time(self._exposure) - self.set_shutter_status(self._shutter_status) + self.set_shutter_status(self._shutter_status) + + self._active_tracks = np.array(self._default_active_tracks) + self._hbin = self._binning[0] + self._vbin = self._binning[1] + self._hstart = self._ROI[0] + self._hend = self._ROI[1] + self._vstart = self._ROI[2] + self._vend = self._ROI[3] def on_deactivate(self): """ - Deinitialisation performed during deactivation of the module. + De-initialisation performed during deactivation of the module. """ - #self.stop_acquisition() + if not (self.get_ready_state()): + self.stop_acquisition() + self.set_shutter_status('CLOSE') + self.check(self.dll.ShutDown()) - # à reprendre - # self._set_shutter(0, 0, 0.1, 0.1) - self.dll.ShutDown() + ############################################################################## + # Error management + ############################################################################## + # is working + # secured OK - tested PV - SI OK -############################################################################## -# Error management -############################################################################## -# is working def check(self, func_val): """ Check routine for the received error codes. :return: the dll function error code @@ -228,16 +264,17 @@ def check(self, func_val): """ if not func_val == 20002: - self.log.error('Error in Newton with errorcode {0}:\n' - '{1}'.format(func_val, self.errorcode[func_val])) + self.log.error('Error in Newton with error_code {0}:\n' + '{1}'.format(func_val, self.error_code[func_val])) return func_val - def _create_errorcode(self): - """ Create a dictionary with the errorcode for the device. + def _create_error_code(self): + """ Create a dictionary with the error_code for the device. """ - maindir = get_main_dir() + main_dir = get_main_dir() + content = [] - filename = os.path.join(maindir, 'hardware', 'camera', 'andor', 'errorcodes_newton.h') + filename = os.path.join(main_dir, 'hardware', 'camera', 'andor', 'errorcodes_newton.h') try: with open(filename) as f: content = f.readlines() @@ -245,45 +282,57 @@ def _create_errorcode(self): self.log.error('No file "errorcodes_newton.h" could be found in the ' 'hardware/camera/andor/ directory!') - errorcode = {} + error_code = {} for line in content: if '#define ' in line: - errorstring, errorvalue = line.split()[-2:] - errorcode[int(errorvalue)] = errorstring + error_string, error_value = line.split()[-2:] + error_code[int(error_value)] = error_string - return errorcode + return error_code -############################################################################## -# Basic functions -############################################################################## + ############################################################################## + # Basic functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_constraint(self): - """Returns all the fixed parameters of the hardware which can be used by the logic. + """ + Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], - 'acquistion_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], + 'acquisition_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], 'shutter_mode_list' : ['CLOSE', 'OPEN'...] 'image_size' : (512, 2048), - 'pixiel_size' : (1e-4, 1e-4), - 'name' : 'Newton940'} + 'pixel_size' : (1e-4, 1e-4), + 'name' : 'Newton940' + 'preamp_gain': authorized gain value (floats)} + + Tested : yes + SI check : yes """ - dico={} - dico['read_mode_list'] =['FVB', 'RANDOM_TRACK', 'SINGLE_TRACK', 'IMAGE'] - dico['acquistion_mode_list'] = ['SINGLE_SCAN'] - dico['trigger_mode_list'] = ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'] - dico['shutter_mode_list'] = ['AUTO', 'OPEN', 'CLOSE'] - dico['image_size'] = self.get_image_size() - dico['pixiel_size'] = self.get_pixel_size() - dico['name'] = self.get_name() - dico['Pream Gain'] = [1, 2, 4] - return dico + constraints = { + 'read_mode_list': ['FVB', 'RANDOM_TRACK', 'IMAGE'], + 'acquisition_mode_list': 'SINGLE_SCAN', + 'trigger_mode_list': ('INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', + 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'), + 'shutter_mode_list': ('AUTO', 'OPEN', 'CLOSE'), + 'image_size': self.get_image_size(), + 'pixel_size': self.get_pixel_size(), + 'name': self.get_name(), + 'preamp_gain': [1, 2, 4] + } + + return constraints def start_acquisition(self): """ + Starts the acquisition :return: nothing - Tested : no + Tested : yes + SI check : yes """ self.check(self.dll.StartAcquisition()) self.dll.WaitForAcquisition() @@ -291,9 +340,10 @@ def start_acquisition(self): def stop_acquisition(self): """ - Stops/aborts live or single acquisition - + Stops/aborts the acquisition @return nothing + tested : yes + SI check : yes """ self.check(self.dll.AbortAcquisition()) return @@ -303,65 +353,57 @@ def get_acquired_data(self): @return numpy array: image data in format [[row],[row]...] - Each pixel might be a float, integer or sub pixels + tested : yes, but without graphic tool... that may highlight more problems + SI check : yes """ + dim = 0 + h = 0 - if self._acquisition_mode == 'SINGLE_SCAN': + if self._acquisition_mode == 'SINGLE_SCAN': # for those who would like to add more acquisition modes if self._read_mode == 'FVB': dim = self._width - h=1 + h = 1 if self._read_mode == 'RANDOM_TRACK': - dim = self._width*self._number_of_tracks - h=self._number_of_tracks - - if self._read_mode == 'SINGLE_TRACK': - dim = self._width - h=1 + dim = self._width * self._number_of_tracks + h = self._number_of_tracks if self._read_mode == 'IMAGE': - dim = self._width*self._height - h=self._height + dim = self._width * self._height + h = self._height dim = int(dim) image_array = np.zeros(dim) - cimage_array = c_int * dim - cimage = cimage_array() - + c_image_array = c_int * dim + c_image = c_image_array() - error_code = self.dll.GetAcquiredData(pointer(cimage), dim) + error_code = self.dll.GetAcquiredData(pointer(c_image), dim) if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Couldn\'t retrieve an image. {0}'.format(ERROR_DICT[error_code])) + self.log.warning('Could not retrieve an image. {0}'.format(ERROR_DICT[error_code])) else: - self.log.debug('image length {0}'.format(len(cimage))) - for i in range(len(cimage)): - # could be problematic for 'FVB' or 'SINGLE_TRACK' readmode - image_array[i] = cimage[i] + self.log.debug('image length {0}'.format(len(c_image))) + for i in range(len(c_image)): + image_array[i] = c_image[i] image_array = np.reshape(image_array, (self._width, h)) - self._cur_image = image_array return image_array - - - - -############################################################################## -# Read mode functions -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Read mode functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_read_mode(self): """ Getter method returning the current read mode used by the camera. - :return: @str read mode (must be compared to a dict) - - The function GetReadMode does not exist in Andor SDK... surprising ! - We have to use a local variable. + :return: @str read mode + tested : yes + SI check : yes """ return self._read_mode @@ -370,82 +412,113 @@ def set_read_mode(self, read_mode): """ Setter method setting the read mode used by the camera. - :param read_mode: @str read mode (must be compared to a dict) + :param read_mode: @str read mode among those defined in the self.get_constraint :return: nothing """ - if hasattr(ReadMode, read_mode): + + if hasattr(ReadMode, read_mode) and (read_mode in self._constraints['read_mode_list']): n_mode = c_int(getattr(ReadMode, read_mode).value) - error_code = self.dll.SetReadMode(n_mode) - if read_mode == 'IMAGE': - self.log.debug("width:{0}, height:{1}".format(self._width, self._height)) - self.set_active_image(1, 1, 1, self._width, 1, self._height) - self._read_mode = read_mode + self.check(self.dll.SetReadMode(n_mode)) + else: + self.log.warning('HW/Newton940/set_read_mode() : read_mode not supported') + return + + self._read_mode = read_mode + + if read_mode == 'IMAGE': + self.set_active_image(1, 1, 1, self._width, 1, self._height) + + elif read_mode == 'RANDOM_TRACK': + self.set_active_tracks(self._active_tracks) return def get_readout_speed(self): + """ + :return: @float : the readout_speed (Horizontal shift) in Hz + tested : yes + SI check : yes + """ return self._readout_speed def set_readout_speed(self, readout_speed): + """ + + :param readout_speed: @float Horizontal shift in Hz + :return: nothing + tested : yes + SI check : yes + """ if readout_speed in list(READOUT_SPEED_DICT.values()): - readout_speed_index=list(READOUT_SPEED_DICT.values()).index(readout_speed) - self.check(self.dll.SetHSSpeed(0,readout_speed_index)) + readout_speed_index = list(READOUT_SPEED_DICT.values()).index(readout_speed) + self.check(self.dll.SetHSSpeed(0, readout_speed_index)) self._readout_speed = readout_speed return else: - self.log.warning('Hardware / Newton940 / set.readout_speed : readout_speed value is not available') - + self.log.warning('Hardware / Newton940 / set.readout_speed : readout_speed value is not available, ' + 'please check self.get_constraints') + return def get_active_tracks(self): - """Getter method returning the read mode tracks parameters of the camera. + """ + Getter method returning the read mode tracks parameters of the camera. - @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @return: (np array) active tracks positions [1st track start-row, 1st track end-row, ... ] + tested : yes + SI check : yes """ - if self._read_mode == 'SINGLE_TRACK' or self._read_mode == 'RANDOM_TRACK': + if self._read_mode == 'RANDOM_TRACK': return self._active_tracks else: - self.log.error('you are not in SINGLE_TRACK or RANDOM_TRACK read_mode') + self.log.error('you are not RANDOM_TRACK read_mode') return def set_active_tracks(self, active_tracks): """ Setter method setting the read mode tracks parameters of the camera. - @param active_tracks: (numpy array of int32) active tracks positions [1st track start, 1st track end, ... ] + @param active_tracks: (numpy array of int32) active tracks + positions [1st track start-row, 1st track end-row, ... ] @return: nothing + tested : yes + SI check : yes """ + if (active_tracks.size % 2) != 0: + self.log.error('Hardware / Newton940 / set.active_tracks :' + 'check your active tracks array : size should be even !') + return - number_of_tracks = int(len(active_tracks)/2) + number_of_tracks = int(len(active_tracks) / 2) self.dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] if self._read_mode == 'FVB': - self.log.error('you want to define acquisition track, but current read_mode is FVB') - elif self._read_mode == 'MULTI_TRACK': - self.log.error('Please use RANDOM TRACK read mode for multi-track acquisition') + self.log.error('Hardware / Newton940 / set.active_tracks : ' + 'you want to define acquisition track, but current read_mode is FVB') elif self._read_mode == 'IMAGE': - self.log.error('you want to define acquisition track, but current read_mode is IMAGE') - elif self._read_mode == 'SINGLE_TRACK' and number_of_tracks == 1: - self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) + self.log.error('Hardware / Newton940 / set.active_tracks :' + 'you want to define acquisition track, but current read_mode is IMAGE') elif self._read_mode == 'RANDOM_TRACK': self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) - else: - self.log.error('problem with active tracks setting') - self._active_tracks=active_tracks - self._number_of_tracks=number_of_tracks + self._active_tracks = active_tracks + self._number_of_tracks = number_of_tracks return def get_active_image(self): - """Getter method returning the read mode image parameters of the camera. + """ + Getter method returning the read mode image parameters of the camera. - @return: (ndarray) active image parameters [hbin, vbin, hstart, hend, vstart, vend] + @return: (np array) active image parameters [hbin, vbin, hstart, hend, vstart, vend] + tested : yes + SI check : yes """ active_image_parameters = [self._hbin, self._vbin, self._hstart, self._hend, self._vstart, self._vend] return active_image_parameters - def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): - """Setter method setting the read mode image parameters of the camera. + def set_active_image(self, hbin, vbin, hstart, hend, vstart, vend): + """ + Setter method setting the read mode image parameters of the camera. @param hbin: (int) horizontal pixel binning @param vbin: (int) vertical pixel binning @@ -454,13 +527,14 @@ def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): @param vstart: (int) image starting column @param vend: (int) image ending column @return: nothing + tested : yes + SI check : yes """ - hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin), \ - c_int(hstart), c_int(hend), c_int(vstart), c_int(vend) + hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin), c_int(hstart), c_int(hend),\ + c_int(vstart), c_int(vend) - error_code = self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend) - msg = ERROR_DICT[error_code] - if msg == 'DRV_SUCCESS': + error_code = self.check(self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend)) + if ERROR_DICT[error_code] == 'DRV_SUCCESS': self._hbin = hbin.value self._vbin = vbin.value self._hstart = hstart.value @@ -469,20 +543,26 @@ def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): self._vend = vend.value self._width = int((self._hend - self._hstart + 1) / self._hbin) self._height = int((self._vend - self._vstart + 1) / self._vbin) + self._ROI = (self._hstart, self._hend, self._vstart, self._vend) + self._binning = (self._hbin, self._vbin) else: - self.log.error('Call to SetImage went wrong:{0}'.format(msg)) + self.log.error('Hardware / Newton940 / set_active_image :' + 'Call to the function went wrong:{0}'.format(ERROR_DICT[error_code])) return -############################################################################## -# Acquisition mode functions -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Acquisition mode functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_acquisition_mode(self): """ Getter method returning the current acquisition mode used by the camera. :return: @str acquisition mode (must be compared to a dict) + tested : yes + SI check : yes """ return self._acquisition_mode @@ -492,59 +572,65 @@ def set_acquisition_mode(self, acquisition_mode): :param acquisition_mode: @str read mode (must be compared to a dict) :return: nothing + tested : yes + SI check : yes """ - if hasattr(AcquisitionMode, acquisition_mode): + if hasattr(AcquisitionMode, acquisition_mode) \ + and (acquisition_mode in self._constraints['acquisition_mode_list']): n_mode = c_int(getattr(AcquisitionMode, acquisition_mode).value) self.check(self.dll.SetAcquisitionMode(n_mode)) else: - self.log.warning('{0} mode is not supported'.format(acquisition_mode)) - + self.log.warning('HW/Newton940/set_acquisition_mode() : ' + '{0} mode is not supported'.format(acquisition_mode)) + return self._acquisition_mode = acquisition_mode - return def get_exposure_time(self): - """ Get the exposure time in seconds + """ + Get the exposure time in seconds - @return float exposure time + @return float exposure time in s + tested : yes + SI check : yes """ exposure = c_float() accumulate = c_float() kinetic = c_float() - error_code = self.dll.GetAcquisitionTimings(byref(exposure), - byref(accumulate), - byref(kinetic)) + self.check(self.dll.GetAcquisitionTimings(byref(exposure), byref(accumulate), byref(kinetic))) self._exposure = exposure.value - self._accumulate = accumulate.value - self._kinetic = kinetic.value return self._exposure def set_exposure_time(self, exposure_time): """ Set the exposure time in seconds - @param float time: desired new exposure time + @param float exposure_time: desired new exposure time - @return float: setted new exposure time + @return float: new exposure time + tested : yes + SI check : yes """ - # faire test sur type de exposure time - - # self.dll.SetExposureTime.argtypes = [ct.c_float] - - code = self.check(self.dll.SetExposureTime(c_float(exposure_time))) + if exposure_time < 0: + self.log.error('HW/Newton940/set_exposure_time() : exposure_time is negative !!') + return + elif exposure_time > self._max_exposure_time: + self.log.error('HW/Newton940/set_exposure_time() : ' + 'exposure time is above the high limit : {0}'.format(self._max_exposure_time)) + return - if code == 20002: - self._exposure = exposure_time - return True - else: - self.log.error('Error during set_exposure_time') + self.check(self.dll.SetExposureTime(c_float(exposure_time))) + self._exposure = exposure_time def get_gain(self): - """ Get the gain + """ + Get the gain @return float: exposure gain + tested : yes + SI check : yes """ return self._preamp_gain @@ -554,26 +640,31 @@ def set_gain(self, gain): @param float gain: desired new gain @return float: new exposure gain + tested : yes + SI check : yes """ - + gain = int(gain) if gain in list(GAIN_DICT.values()): - gain_index=list(GAIN_DICT.values()).index(gain) + gain_index = list(GAIN_DICT.values()).index(gain) self.check(self.dll.SetPreAmpGain(gain_index)) self._preamp_gain = gain return else: - self.log.warning('Hardware / Newton940 / set.gain : gain value is not available') + self.log.warning('HW/Newton940/set_gain() : gain value is not available') -############################################################################## -# Trigger mode functions -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Trigger mode functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_trigger_mode(self): """ Getter method returning the current trigger mode used by the camera. :return: @str trigger mode (must be compared to a dict) + tested : yes + SI check : yes """ return self._trigger_mode @@ -583,25 +674,34 @@ def set_trigger_mode(self, trigger_mode): :param trigger_mode: @str trigger mode (must be compared to a dict) :return: nothing + tested : yes + SI check : yes """ - if hasattr(TriggerMode, trigger_mode): + if hasattr(TriggerMode, trigger_mode) \ + and (trigger_mode in self._constraints['trigger_mode_list']): n_mode = c_int(getattr(TriggerMode, trigger_mode).value) self.check(self.dll.SetTriggerMode(n_mode)) self._trigger_mode = trigger_mode else: - self.log.warning('{0} mode is not supported'.format(trigger_mode)) + self.log.warning('HW/Newton940/set_trigger_mode() : ' + '{0} mode is not supported'.format(trigger_mode)) + return + self._trigger_mode = trigger_mode return -############################################################################## -# Shutter mode functions -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Shutter mode functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_shutter_status(self): """ Getter method returning if the shutter is open. :return: @bool shutter open ? + tested : yes + SI check : yes """ return self._shutter_status @@ -609,27 +709,38 @@ def set_shutter_status(self, shutter_status): """ Setter method setting if the shutter is open. - :param shutter_mode: @bool shutter open + :param shutter_status: @string :return: nothing + tested : yes + SI check : yes """ - if hasattr(ShutterMode, shutter_status): + if hasattr(ShutterMode, shutter_status) \ + and (shutter_status in self._constraints['shutter_mode_list']): mode = c_int(getattr(ShutterMode, shutter_status).value) - self.dll.SetShutter(self._shutter_TTL, mode, self._shutter_closing_time, self._shutter_opening_time) + self.check(self.dll.SetShutter(self._shutter_TTL, mode, + self._shutter_closing_time, self._shutter_opening_time)) self._shutter_status = shutter_status - + else: + self.log.warning('HW/Newton940/set_shutter_status() : ' + '{0} mode is not supported'.format(shutter_status)) + return + self._shutter_status = shutter_status return -############################################################################## -# Temperature functions -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Temperature functions + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_cooler_status(self): """ Getter method returning the cooler status if ON or OFF. :return: @bool True if ON or False if OFF or 0 if error + tested : yes + SI check : yes """ return self._cooler_status @@ -639,41 +750,54 @@ def set_cooler_status(self, cooler_status): :cooler_ON: @bool True if ON or False if OFF :return: nothing + tested : yes + SI check : yes """ if cooler_status: self.check(self.dll.CoolerON()) - self._cooler_status=True + self._cooler_status = True else: self.check(self.dll.CoolerOFF()) - self._cooler_status=False + self._cooler_status = False return def get_temperature(self): """ Getter method returning the temperature of the camera. - :return: @float temperature (°C) or 0 if error + :return: @float temperature (K) or 0 if error + tested : yes + SI check : yes """ temp = c_int32() self.dll.GetTemperature(byref(temp)) - return temp.value + return temp.value + 273.15 def set_temperature(self, temperature): """ Getter method returning the temperature of the camera. - :param temperature: @float temperature (°C) or 0 if error + :param temperature: @float temperature (K) or 0 if error :return: nothing - """ - tempperature = c_int32(temperature) - self.dll.SetTemperature(temperature) + tested : yes + SI check : yes + """ + temperature = int(temperature) + if self._min_temperature < temperature < self._max_temperature: + temperature = int(temperature-273.15) + self.check(self.dll.SetTemperature(temperature)) + self._temperature = temperature+273.15 + else: + self.log.warning('HW/Newton940/set_temperature() : temperature is not in the validity range ') + return -############################################################################## -# Internal functions, for constraints preparation -############################################################################## -# is working, but not secured and SI + ############################################################################## + # Internal functions, for constraints preparation + ############################################################################## + # is working + # secured OK - tested PV - SI OK def get_name(self): """ @@ -691,8 +815,8 @@ def get_image_size(self): :return: tuple (nw_px, ny_px) : int number of pixel along x and y axis - Tested : no - SI check : ok + Tested : yes + SI check : yes """ nx_px = ct.c_int() ny_px = ct.c_int() @@ -701,7 +825,9 @@ def get_image_size(self): def get_pixel_size(self): """ - :return: + :return: tuple (float) (x,y) pixel size unit is meter + tested : yes + SI : yes """ x_px = ct.c_float() y_px = ct.c_float() @@ -710,54 +836,37 @@ def get_pixel_size(self): def get_ready_state(self): """ - - :return: + :return: Bool =True if camera state is idle + tested : yes + SI : yes """ code = ct.c_int() self.check(self.dll.GetStatus(byref(code))) - if code.value == 20073: + if ERROR_DICT[code.value] != 'DRV_SUCCESS': return True else: return False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + def get_current_config(self): + """ + :return: dictionary with camera current configuration. + please see also get_constraints() + """ + config = { + 'camera ID..................................': self.get_name(), + 'sensor size (pixels).......................': self.get_image_size(), + 'pixel size (m)............................': self.get_pixel_size(), + 'acquisition mode...........................': self._acquisition_mode, + 'read mode..................................': self._read_mode, + 'readout speed (Hz).........................': self._readout_speed, + 'gain (x)...................................': self._preamp_gain, + 'trigger_mode...............................': self._trigger_mode, + 'exposure_time..............................': self._exposure, + 'ROI geometry (readmode = IMAGE)............': self._ROI, + 'ROI binning (readmode = IMAGE).............': self._binning, + 'number of tracks (readmode = RANDOM TRACK).': self._number_of_tracks, + 'tracks definition (readmode = RANDOM TRACK)': self._active_tracks, + 'temperature (K)............................': self._temperature, + 'shutter_status.............................': self._shutter_status, + } + return config From e8ecf82a82e1224cabb4e49b9ddebe3a57a9bf95 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 22 Apr 2020 16:28:30 +0200 Subject: [PATCH 21/49] GUI module + ui update (old version) Don't care about this commit, this will be changed soon --- gui/PLspectrum/PL_spectrum_gui.py | 111 ++-- gui/PLspectrum/ui_hirondelle200.ui | 934 +++++++++++++++-------------- 2 files changed, 547 insertions(+), 498 deletions(-) diff --git a/gui/PLspectrum/PL_spectrum_gui.py b/gui/PLspectrum/PL_spectrum_gui.py index a6dd475bad..ba19e1a530 100644 --- a/gui/PLspectrum/PL_spectrum_gui.py +++ b/gui/PLspectrum/PL_spectrum_gui.py @@ -95,7 +95,7 @@ def on_activate(self): # label plot axis : - self._spec.setLabel('left', 'Fluorescence', units='counts/s') + self._spec.setLabel('left', 'Signal intensity', units='counts/s') self._spec.setLabel('right', 'Number of Points', units='#') self._spec.setLabel('bottom', 'Wavelength', units='m') self._spec.setLabel('top', 'Relative Frequency', units='Hz') @@ -119,55 +119,74 @@ def on_activate(self): self._save_PNG = True def read_settings(self): - - self._center_wavelength = self._spectrum_logic.center_wavelength - self._detector_offset = self._spectrum_logic.detector_offset - self._grating = self._spectrum_logic.grating - self._input_slit = self._spectrum_logic.input_slit - self._input_slit_width = self._spectrum_logic.input_slit_width - self._output_slit = self._spectrum_logic.output_slit - self._output_slit_width = self._spectrum_logic.output_slit_width - self._min_wavelength, self._max_wavelength = self._spectrum_logic.wavelength_limits - - self._mw.wavelengthDSpin.setRange(self._min_wavelength, self._max_wavelength) - +""" # Initialize widgets slots : - self._mw.wavelengthDSpin.setValue(self._center_wavelength) - self._mw.detectorOffsetSpin.setValue(self._detector_offset) - self._mw.gratingNumCombo.setCurrentIndex(self._grating) - self._mw.inputSlitCombo.setCurrentIndex(self._input_slit-1) - self._mw.inputSlitWidthDSpin.setValue(self._input_slit_width) - self._mw.outputSlitCombo.setCurrentIndex(self._output_slit-1) - self._mw.outputSlitWidthDSpin.setValue(self._output_slit_width) - + self._mw.gratingNumCombo.setCurrentIndex(self._spectrum_logic.grating) + self._mw.inputPortCombo.setCurrentIndex(self._spectrum_logic.input_port) + self._mw.outputPortCombo.setCurrentIndex(self._spectrum_logic.output_port) + self._mw.readModeCombo.setCurrentIndex(self._spectrum_logic.read_mode) + self._mw.acquModeCombo.setCurrentIndex(self._spectrum_logic.acquisition_mode) + self._mw.triggerModeCombo.setCurrentIndex(self._spectrum_logic.trigger_mode) + self._mw.scanFreqCombo.setCurrentIndex(self._spectrum_logic. + self._mw.accumulationDelayDSpin.setCurrentIndex(self._spectrum_logic. + + self._mw.cameraGainSpin.setValue(self._spectrum_logic._camera_gain) + self._mw.numAccumulatedSpin.setValue(self._spectrum_logic. + self._mw.numTrackSpin.setValue(self._spectrum_logic. + self._mw.trackHeightSpin.setValue(self._spectrum_logic. + self._mw.trackOffsetSpin.setValue(self._spectrum_logic. + self._mw.gratingOffsetSpin.setValue(self._spectrum_logic. + + self._mw.exposureDSpin.setValue(self._spectrum_logic. + self._mw.numScanDSpin.setValue(self._spectrum_logic. + self._mw.temperatureDSpin.setValue(self._spectrum_logic. + self._mw.inputSlitWidthDSpin.setValue(self._spectrum_logic. + self._mw.outputSlitWidthDSpin.setValue(self._spectrum_logic. + self._mw.wavelengthDSpin.setValue(self._spectrum_logic. + + self._mw.coolerButton + + self._mw.wavelengthDSpin.setRange(self._spectrum_logic.wavelength_limit) + """ def update_settings(self): - self._center_wavelength = self._mw.wavelengthDSpin.value() - self._detector_offset = self._mw.detectorOffsetSpin.value() - self._grating = self._mw.gratingNumCombo.currentIndex() - self._input_slit = self._mw.inputSlitCombo.currentIndex()+1 - self._input_slit_width = self._mw.inputSlitWidthDSpin.value() - self._output_slit = self._mw.outputSlitCombo.currentIndex()+1 - self._output_slit_width = self._mw.outputSlitWidthDSpin.value() - - self._spectrum_logic.center_wavelength = self._center_wavelength - self._spectrum_logic.detector_offset = self._detector_offset - self._spectrum_logic.grating = self._grating - self._spectrum_logic.input_slit = self._input_slit - self._spectrum_logic.input_slit_width = self._input_slit_width - self._spectrum_logic.output_slit = self._output_slit - self._spectrum_logic.output_slit_width = self._output_slit_width - - self._min_wavelength, self._max_wavelength = self._spectrum_logic.wavelength_limits - self._mw.wavelengthDSpin.setRange(self._min_wavelength, self._max_wavelength) - - def update_image_settings(self): - - self._width = 2048 # number of pixels along dispersion axis - self._height = 512 # number of pixels (perpendicular to dispersion axis) - self._pixelwidth = 13 # unit is micrometer - self._pixelheight = 13 # unit is micrometer + self._spectrum_logic.center_wavelength = self._mw.wavelengthDSpin.value() + self._spectrum_logic.detector_offset = self._mw.detectorOffsetSpin.value() + self._spectrum_logic.grating = self._mw.gratingNumCombo.currentIndex() + self._spectrum_logic.input_slit = self._mw.inputSlitCombo.currentIndex() + self._spectrum_logic.input_slit_width = self._mw.inputSlitWidthDSpin.value() + self._spectrum_logic.output_slit = self._mw.outputSlitCombo.currentIndex() + self._spectrum_logic.output_slit_width = self._mw.outputSlitWidthDSpin.value() + + self._mw.wavelengthDSpin.setRange(self._spectrum_logic.wavelength_limits) + + self._mw.gratingNumCombo.setCurrentIndex(self._spectrum_logic.grating) + self._mw.inputPortCombo.setCurrentIndex(self._spectrum_logic.input_port) + self._mw.outputPortCombo.setCurrentIndex(self._spectrum_logic.output_port) + self._mw.readModeCombo.setCurrentIndex(self._spectrum_logic.read_mode) + self._mw.acquModeCombo.setCurrentIndex(self._spectrum_logic.acquisition_mode) + self._mw.triggerModeCombo.setCurrentIndex(self._spectrum_logic.trigger_mode) + self._mw.scanFreqCombo.setCurrentIndex(self._spectrum_logic. + self._mw.accumulationDelayDSpin.setCurrentIndex(self._spectrum_logic. + + self._mw.cameraGainSpin.setValue(self._spectrum_logic._camera_gain) + self._mw.numAccumulatedSpin.setValue(self._spectrum_logic. + self._mw.numTrackSpin.setValue(self._spectrum_logic. + self._mw.trackHeightSpin.setValue(self._spectrum_logic. + self._mw.trackOffsetSpin.setValue(self._spectrum_logic. + self._mw.gratingOffsetSpin.setValue(self._spectrum_logic. + + self._mw.exposureDSpin.setValue(self._spectrum_logic. + self._mw.numScanDSpin.setValue(self._spectrum_logic. + self._mw.temperatureDSpin.setValue(self._spectrum_logic. + self._mw.inputSlitWidthDSpin.setValue(self._spectrum_logic. + self._mw.outputSlitWidthDSpin.setValue(self._spectrum_logic. + self._mw.wavelengthDSpin.setValue(self._spectrum_logic. + + self._mw.coolerButton + + self._mw.wavelengthDSpin.setRange(self._spectrum_logic.wavelength_limit) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. diff --git a/gui/PLspectrum/ui_hirondelle200.ui b/gui/PLspectrum/ui_hirondelle200.ui index 6a3747751a..20b64b4d59 100644 --- a/gui/PLspectrum/ui_hirondelle200.ui +++ b/gui/PLspectrum/ui_hirondelle200.ui @@ -10,6 +10,12 @@ 798 + + + 1270 + 0 + + Hirondelle200 @@ -30,50 +36,65 @@ File - + + + Save Data + + + + + + + + Save Data As... + + + + + + Import Data - - + + + - - + + - + Execute - - + + + + + - + - Settings + Windows - - - - - - Display - - - + + + + + Info - + - - + @@ -87,606 +108,617 @@ false - - + + + + + - - + + - + + - + 62 - 38 + 50 + + + + + 700 + 700 + + Qt::LeftToRight + - 2 + 8 - - - Qt::LeftToRight - - - false - - + + 10 10 - 271 - 241 + 261 + 221 - - - + + + QLayout::SetDefaultConstraint + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + - Stop Image + Grating : + + + + + 300(300) + + + + + 1200(300) + + + + + 1800(300) + + + + - + - Run image + Input port : - - - - - 0 - 0 - + + + + + Side + + + + + Front + + + + + + + + Input slit width : - - - 100 - 100 - + + + + + + + + + Output port : - - QGraphicsView::AnchorUnderMouse + + + + + + + Side + + + + + Front + + + + + + + + Output slit width : + + + + + + + + + + Center wavelength : + + + + false + + + + + + + Grating offset : + + + + + + - + 62 - 38 + 50 + + + + + 700 + 700 Qt::LeftToRight + + QDockWidget::AllDockWidgetFeatures + 8 - - + + 10 10 - 501 - 246 + 667 + 221 - - - + + + QLayout::SetDefaultConstraint + + + + + Number of tracks : + + + + + + + Scan frequency : + + + + + + + + + + Exposure time : + + + + + + + + - - - Detector line offset : - - + + INTERNAL + - - - Center wavelength : - - + + EXTERNAL + - - - Grating number : - - + + EXTERNAL START + - - - Input slit : - - + + EXTERNAL EXPOSURE + + + + + + + Tracks height : + + + + + + + Camera gain : + + + + + + + Temperature : + + + + + + + + + + Tracks offset : + + + + + + + Acquisition mode : + + + + + + + + + + + + + Read mode : + + + + + - - - Input slit width : - - + + Single Scan + - - - Output slit : - - + + Accumulate Scan + - - - Output slit width : - - + + Live Scan + - + - - + + - + + FVB + - - - false - - + + SINGLE_TRACK + - - - - 300(300) - - - - - 1200(300) - - - - - 1800(300) - - - + + MULTI_TRACK + + + + + + + Number of scan : + + + + + + + + - - - - Side - - - - - Front - - - + + 3 MHz + - + + 1 MHz + - - - - Side - - - - - Front - - - + + 50 kHz + + + + + - + + 3 MHz + - + + + 1 MHz + + + + + 50 kHz + + + - - - - Qt::Horizontal + + + + Trigger mode : - - - 40 - 20 - + + + + + + + + + Number of accumulated scan : - + + + + + + + + + Accumulation frequency : + + + + + + + Cooler ON + + - + 62 - 38 + 50 - - Qt::LeftToRight - - - QDockWidget::AllDockWidgetFeatures + + + 1000 + 700 + 8 - - + + + Qt::LeftToRight + + + false + + 10 10 - 411 - 325 + 251 + 221 - - - - - - - Read mode : - - - - - - - Number of track : - - - - - - - Tracks height : - - - - - - - Tracks offset : - - - - - - - Acquisition mode : - - - - - - - Exposure time : - - - - - - - Accumulation frequency : - - - - - - - Number of accumulated scan : - - - - - - - Repetition frequency : - - - - - - - Number of repetition : - - - - - - - - - - - - FVB - - - - - SINGLE_TRACK - - - - - MULTI_TRACK - - - - - - - - - - - - - - - - - - Single Scan (1) - - - - - Accumulate Scan (2) - - - - - Kinetic Series (3) - - - - - Live Scan (4) - - - - - Fast Kinetics (5) - - - - - - - - - - - - 3 MHz - - - - - 1 MHz - - - - - 50 kHz - - - - - - - - - - - - 3 MHz - - + + + + + - - 1 MHz - + + + Stop Image + + - - 50 kHz - + + + Run image + + - - - - + + + + - - - - 900 - 50 - - + 1 - - + + 10 10 - 871 - 364 + 1251 + 411 - - - 0 - 0 - - - - - 100 - 100 - - - - QGraphicsView::AnchorUnderMouse - + + + + + - - - - :/Images/save_logo.png:/Images/save_logo.png - + - Save - - - Save Data + spectrum - - - - :/Images/save_logo.png:/Images/save_logo.png - + - Save As... - - - Save Data As ... + background - - - - :/Images/folder_logo.png:/Images/folder_logo.png - + - Import Data... - - - Import Spectrum Data from a File + image - - - - :/Images/settingslogo.jpg:/Images/settingslogo.jpg - + - Settings - - - Spectrometer Settings + spectrum - - - - :/Images/runLogo.png:/Images/runLogo.png - + - Run + background - - Run Spectrum Acquisition + + + + image - - - - :/Images/doc_logo.jpg:/Images/doc_logo.jpg + + + spectrum + + - Documentation + background - - Documentation + + + + image - - - - :/Images/stop_logo.png:/Images/stop_logo.png + + + Run Spectrum + + - Stop Run + Run Background - + - Camera Settings + Run Image - + - Spectrometer Settings + Spectro. Settings - + - Import Spectrum + Camera Settings - + - Import Background + Spectrum Window - + - Spectrum + Image Window - + - Image + Run Live Acqusition - + Documentation + + + Stop Acquisition + + @@ -695,8 +727,6 @@
pyqtgraph
- - - + From 617cbad81b322f79da7cb40e7612336747f1d199 Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 28 Apr 2020 12:49:39 +0200 Subject: [PATCH 22/49] Logic version 3 (ready for comment and protestation) Correction from last review --- logic/spectrum_logic.py | 1023 +++++++++++++++++++-------------------- 1 file changed, 497 insertions(+), 526 deletions(-) diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 50bc8896b2..5321bb212b 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -30,10 +30,10 @@ from core.util.network import netobtain from logic.generic_logic import GenericLogic from core.configoption import ConfigOption +from logic.save_logic import SaveLogic from datetime import date - class SpectrumLogic(GenericLogic): """This logic module gathers data from the spectrometer. """ @@ -44,28 +44,27 @@ class SpectrumLogic(GenericLogic): savelogic = Connector(interface='SaveLogic') # declare status variables (logic attribute) : - _spectrum_data = StatusVar('spectrum_data', np.empty((2, 0))) - _background_data = StatusVar('background_data', np.empty((2, 0))) - _image_data = StatusVar('image_data', np.empty((2, 0))) + _acquired_data = np.empty((2, 0)) - # Allow to set a default save directory in the config file : - _default_save_file_path = ConfigOption('default_save_file_path') #TODO: Qudi savelogic handle the saving, it's better to let it do it things - _save_file_path = StatusVar('save_file_path', _default_save_file_path) # TODO: same + # declare status variables (spectro attribute) : + _wavelength_calibration = StatusVar('wavelength_calibration', 0) # declare status variables (camera attribute) : - _read_mode = StatusVar('read_mode', 'FVB') - _active_tracks = StatusVar('active_tracks', [240, 240]) # TODO: some camera have only one pixel height, or not support anything else than FVB - - _acquistion_mode = StatusVar('acquistion_mode', 'MULTI_SCAN') - _exposure_time = StatusVar('exposure_time', 1) - _camera_gain = StatusVar('camera_gain', 1) # TODO: even if unlikely, some camera might not have 1 in its possible value + _readout_speed = StatusVar('readout_speed', None) + _camera_gain = StatusVar('camera_gain', None) + _exposure_time = StatusVar('exposure_time', None) + _accumulation_delay = StatusVar('accumulation_delay', 1e-2) + _scan_delay = StatusVar('scan_delay', 1) _number_of_scan = StatusVar('number_of_scan', 1) - _scan_delay = StatusVar('scan_delay', 1e-2) _number_accumulated_scan = StatusVar('number_accumulated_scan', 1) - _accumulation_delay = StatusVar('accumulation_delay', 1e-3) + + _acquisition_mode = StatusVar('acquisition_mode', 'SINGLE_SCAN') _trigger_mode = StatusVar('trigger_mode', 'INTERNAL') + # cosmic rejection coeff : + _coeff_rej_cosmic = StatusVar('coeff_cosmic_rejection', 2.2) + ############################################################################## # Basic functions ############################################################################## @@ -76,176 +75,161 @@ def __init__(self, **kwargs): @param dict kwargs: optional parameters """ super().__init__(**kwargs) - self.threadlock = Mutex() # TODO: This line on its own does nothing def on_activate(self): """ Initialisation performed during activation of the module. """ - self.spectrometer_device = self.spectrometer() #TODO: New modules prefer the syntax self.spectrometer() directly in the code rather than storing a second reference in a variable - self.camera_device = self.camera() self._save_logic = self.savelogic() # hardware constraints : - #TODO: You don't need to copy every entry to the module attributes, you can just use self.constraints['auto_slit_installed'] - # You can merge the two dictionaries or keep them separate - spectro_constraints = self.spectrometer_device.get_constraints() - self._number_of_gratings = spectro_constraints['number_of_gratings'] - self._wavelength_limits = spectro_constraints['wavelength_limits'] - self._auto_slit_installed = spectro_constraints['auto_slit_installed'] - self._flipper_mirror_installed = spectro_constraints['flipper_mirror_installed'] - - camera_constraints = self.camera_device.get_constraints() - self._read_mode_list = camera_constraints['read_mode_list'] - self._acquisition_mode_list = camera_constraints['acquisition_mode_list'] - self._trigger_mode_list = camera_constraints['trigger_mode_list'] - self._shutter_mode_list = camera_constraints['shutter_mode_list'] - self._image_size = camera_constraints['image_size'] - self._pixel_size = camera_constraints['pixel_size'] - - # Spectrometer calibration using camera contraints parameters: - self.spectrometer_device.set_calibration(self._image_size[1], self._pixel_size[1], self._image_size[0]/2) #TODO - - # declare spectrometer attributes : - # grating : - # TODO: Here you can initialize the hidden variable with : - # self._attribute = self.spectrometer().get_attribute() - # and then in the logic getter just return : self._attribute - # Here is way it is initialize works but it quite unusual - self._grating = self.grating - # self._grating = self.spectrometer().get_grating() #todo and then in the grating property return self._grating directly - self._grating_offset = self.grating_offset - - #wavelenght : - self._center_wavelength = self.center_wavelength - self._wavelength_range = self.wavelength_range - - self._input_port = self.input_port - self._output_port = self.output_port - self._input_slit_width = self.input_slit_width - self._output_slit_width = self.output_slit_width - - # declare camera attributes : - self._active_image = self._image_size #TODO This line surprise me from the variable name - - self._shutter_mode = self.shutter_mode - self._cooler_status = self.cooler_status - self._camera_temperature = self.camera_temperature + self.spectro_constraints = self.spectrometer().get_constraints() + self.camera_constraints = self.camera().get_constraints() + + self._acquisition_mode_list = ['SINGLE_SCAN', 'MULTI_SCAN', 'LIVE_SCAN', 'ACC_MULTI_SCAN', + 'ACC_LIVE_SCAN'] + + # gratings : + self._grating_number = self.spectrometer().get_grating_number() + + # wavelength : + self._center_wavelength = self.spectrometer().get_wavelength() + + # spectro configurations : + self._input_port = self.spectrometer().get_input_port() + self._output_port = self.spectrometer().get_output_port() + self._input_slit_width = self.spectrometer().get_input_slit_width() + self._output_slit_width = self.spectrometer().get_output_slit_width() + + # read mode : + self._read_mode = self.camera().get_read_mode() + self._active_tracks = self.camera().get_active_tracks() + self._active_image = self.camera().get_active_image() + + if self._camera_gain==None: + self._camera_gain = self.camera().get_gain() + + if self._exposure_time==None: + self._exposure_time = self.camera().get_exposure_time() + + if self._readout_speed==None: + self._readout_speed = self.camera().get_readout_speed() # QTimer for asynchronous execution : self._timer = QtCore.QTimer() - self._timer.timeout.connect(self.acquire_data) - self._counter = 0 + self._timer.singleShot(True) + self._timer.timeout.connect(self.loop_acquisition) + self._loop_counter = 0 def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ - if self.module_state() != 'idle' and self.module_state() != 'deactivated': #TODO: if the module is not idle, this function needs to stop the acquisition, it can not disobey ! + if self.module_state() != 'idle' and self.module_state() != 'deactivated': + self.stop_acquisition() pass - ############################################################################## - # Save functions - ############################################################################## - - def save_data(self, data_type = 'spectrum', file_name = None, save_path = None): - """Method used to save the data using the savelogic module - - @param data_type: (str) must be 'image', 'spectrum' or 'background' - @param file_name: (str) name of the saved file - @param save_path: (str) relative path where the file should be saved - @return: nothing - """ - data = OrderedDict() - today = date.today() - if data_type == 'image': - data = self._image_data[:, :] # TODO: tab[:, :] is equivalent to just tab - if data_type == 'spectrum': - data['Wavelength (m)'] = self._spectrum_data[0, :] #TODO: It's better to use simple variable-like name for this. This will be clear the day the data are treated ! - data['PL intensity (u.a)'] = self._spectrum_data[1, :] #TODO: just "counts" would be more appropriate, it's not (u.a.) - elif data_type == 'background': - data['Wavelength (m)'] = self._background_data[0, :] - data['PL intensity (u.a)'] = self._background_data[1, :] - else: - self.log.debug('Data type parameter is not defined : it can only be \'spectrum\',' #TODO: this should be an error - ' \'background\' or \'image\'') - if file_name is not None: - file_label = file_name - else: - file_label = '{}_'.format(data_type) + today.strftime("%d%m%Y") #TODO: Savelogic already does timestamping - if save_path is not None: - self._save_file_path = save_path - self._save_logic.save_data(data, - filepath=self._save_file_path, - filelabel=file_label) - self.log.info('{} data saved in {}'.format(data_type.capitalize(), self._save_file_path)) - ############################################################################## # Acquisition functions ############################################################################## - def start_spectrum_acquisition(self): #TODO: In the code this function also starts image - """ Start acquisition by lauching the timer signal calling the 'acquire_data' function. + def start_acquisition(self): + """ Start acquisition by launching the timer signal calling the 'acquire_data' function. """ - self._counter = 0 - self.module_state.lock() #TODO: this method should check if the module is already soemthing - self._timer.start(1000 * self._scan_delay) #The argument of QTimer.start() is in ms #TODO: Why is the acquisition not started right away ? What exactly is _scan_delay ? - - def acquire_data(self): - """ Method acquiring data by using the camera hardware methode 'start_acquistion'. This method is connected + if self.module_state() == 'locked': + self.log.error("Module acquisition is still running, wait before launching a new acquisition " + ": module state is currently locked. ") + return + self.module_state.lock() + if self._acquisition_mode == "SINGLE_SCAN": + self.camera().start_acquisition() + self.module_state.unlock() + self._acquired_data = self.get_acquired_data() + self.log.info("Acquisition finished : module state is 'idle' ") + return + self._loop_counter = 0 + self.loop_acquisition() + + def loop_acquisition(self): + """ Method acquiring data by using the camera hardware method 'start_acquisition'. This method is connected to a timer signal : after timer start this slot is called with a period of a time delay. After a certain number of call this method can stop the timer if not in 'LIVE' acquisition. """ - self.shutter_mode('OPEN') #TODO: Some hardware do not have a shutter - self.camera_device.start_acquisition() - self.shutter_mode('CLOSE') # TODO: start_acquisition only starts it, this line is executed while it is still ongoing - self._counter += 1 - if self._read_mode == 'IMAGE': - self._image_data = np.concatenate(self._image_data, self.acquired_data) #TODO: I don't understand what this tries to do - name = 'Image' - else: - self._spectrum_data = np.concatenate(self._spectrum_data, self.acquired_data) - name = 'Spectrum' - if (self._number_of_scan - 1 < self._counter) and self._acquisition_mode != 'LIVE': + self.camera().start_acquisition() + self._acquired_data = self.get_acquired_data() + if self._acquisition_mode[:3] == 'ACC': + if self._loop_counter%self._number_accumulated_scan == 0: + data = self._acquired_data[-self._number_accumulated_scan] + np.delete(self._acquired_data, np.s_[-self._number_accumulated_scan], axis=0) + self._acquired_data = self.reject_cosmic(data) + delay_time = self._scan_delay + else: + delay_time = self._accumulation_delay + if self._acquisition_mode[-10:-5] == 'MULTI' and self._loop_counter%self._number_of_scan == 0: + self._timer.stop() self.module_state.unlock() - self._timer.timeout.stop() - self.log.info("{} acquisition succeed ! Number of acquired scan : {} " - "/ Delay between each scan : {}".format(name, self._number_of_scan, self._scan_delay)) + self.log.info("Acquisition finished : module state is 'idle' ") + else: + delay_time = self._scan_delay + self._timer.start(delay_time) + + + def reject_cosmic(self, data): + """This function is used to reject cosmic features from acquired spectrum by computing the standard deviation + of an ensemble of accumulated scan parametrized by the number_accumulated_scan and the accumulation_delay + parameters. The rejection is carry out with a mask rejecting values outside their standard deviation with a + weight given by a coeff coeff_rej_cosmic. This method should be only used in "accumulation mode". + + """ + if len(data) 0: + self.log.debug("Exposure time parameter must be a positive number ") + return + self.camera().set_exposure_time(exposure_time) + self._exposure_time = self.camera().get_exposure_time() @property def accumulation_delay(self): @@ -712,7 +699,6 @@ def accumulation_delay(self): @return: (float) accumulation delay """ - self._accumulation_delay = self.camera_device.get_accumulation_delay() return self._accumulation_delay @accumulation_delay.setter @@ -722,22 +708,48 @@ def accumulation_delay(self, accumulation_delay): @param accumulation_delay: (float) accumulation delay @return: nothing """ - if not isinstance(accumulation_delay, float): - self.log.debug("Accumulation time parameter must be a float ") - break + if self.module_state() == 'locked': + self.log.error("Acquisition process is currently running : you can't change this parameter" + " until the acquisition is completely stopped ") + return + accumulation_delay = float(accumulation_delay) if not accumulation_delay > 0 : - self.log.debug("Accumulation time parameter must be a positive number ") - break + self.log.debug("Accumulation delay parameter must be a positive number ") + return if not self._exposure_time < accumulation_delay < self._scan_delay: - self.log.debug("Accumulation time parameter must be a value between" + self.log.debug("Accumulation delay parameter must be a value between" "the current exposure time and scan delay values ") - break - if not accumulation_delay == self._accumulation_delay: - self.log.info("Accumulation time parameter has not be changed ") - break + return self._accumulation_delay = accumulation_delay - self.camera_device.set_accumulation_delay(accumulation_delay) - self.log.info('Accumulation delay has been set correctly ') + + @property + def scan_delay(self): + """Getter method returning the scan delay between consecutive scan during multiple acquisition mode. + + @return: (float) scan delay + """ + return self._scan_delay + + @scan_delay.setter + def scan_delay(self, scan_delay): + """Setter method setting the scan delay between consecutive scan during multiple acquisition mode. + + @param scan_delay: (float) scan delay + @return: nothing + """ + if self.module_state() == 'locked': + self.log.error("Acquisition process is currently running : you can't change this parameter" + " until the acquisition is completely stopped ") + return + scan_delay = float(scan_delay) + if not scan_delay > 0: + self.log.debug("Scan delay parameter must be a positive number ") + return + if not self._exposure_time < self._scan_delay: + self.log.debug("Scan delay parameter must be a value bigger than" + "the current exposure time ") + return + self._scan_delay = scan_delay @property def number_accumulated_scan(self): @@ -745,7 +757,6 @@ def number_accumulated_scan(self): @return: (int) number of accumulated scan """ - self._number_accumulated_scan = self.camera_device.get_number_accumulated_scan() return self._number_accumulated_scan @number_accumulated_scan.setter @@ -755,81 +766,40 @@ def number_accumulated_scan(self, number_scan): @param number_scan: (int) number of accumulated scan @return: nothing """ - if not isinstance(number_scan, int): - self.log.debug("Number of accumulated scan parameter must be an integer ") - break + if self.module_state() == 'locked': + self.log.error("Acquisition process is currently running : you can't change this parameter" + " until the acquisition is completely stopped ") + return + number_scan = int(number_scan) if not number_scan > 0: self.log.debug("Number of accumulated scan parameter must be positive ") - break - if not number_scan == self._number_of_scan: - self.log.info("Number of accumulated scan parameter has not be changed ") - break + return self._number_accumulated_scan = number_scan - self.camera_device.set_number_accumulated_scan(number_scan) - self.log.info('Number of accumulated scan has been set correctly ') - - @property - def exposure_time(self): - """ Get the exposure time in seconds - - @return: (float) exposure time - """ - self._exposure_time = self.camera_device.get_exposure_time() - return self._exposure_time - - @exposure_time.setter - def exposure_time(self, exposure_time): - """ Set the exposure time in seconds. - - @param exposure_time: (float) desired new exposure time - - @return: nothing - """ - if not isinstance(exposure_time, float): - self.log.debug("Exposure time parameter must be a float ") - break - if not exposure_time > 0: - self.log.debug("Exposure time parameter must be a positive number ") - break - if not exposure_time < self._accumulation_delay: #TODO: This is confusing - self.log.debug("Exposure time parameter must be a value lower" - "that the current accumulation time values ") - break - if not exposure_time == self._exposure_time: - self.log.info("Exposure time parameter has not be changed ") - break - self._exposure_time = exposure_time - self.camera_device.set_exposure_time(exposure_time) - self.log.info('Exposure time has been set correctly ') @property - def camera_gain(self): - """ Get the gain. + def number_of_scan(self): + """Getter method returning the number of acquired scan during multiple acquisition mode. - @return: (float) exposure gain + @return: (int) number of acquired scan """ - self._camera_gain = self.camera_device.get_camera_gain() - return self._camera_gain - - @camera_gain.setter - def camera_gain(self, camera_gain): - """ Set the gain. + return self._number_of_scan - @param camera_gain: (float) desired new gain + @number_of_scan.setter + def number_of_scan(self, number_scan): + """Setter method setting the number of acquired scan during multiple acquisition mode. + @param number_scan: (int) number of acquired scan @return: nothing """ - if not isinstance(camera_gain, float): - self.log.debug("Camera gain parameter must be a float ") - break - if not camera_gain > 0: - self.log.debug("Camera gain parameter must be a positive number ") - break - if not camera_gain == self._camera_gain: - self.log.info("Camera gain parameter has not be changed ") - self._camera_gain = camera_gain - self.camera_device.set_camera_gain(camera_gain) - self.log.info('Camera gain has been set correctly ') + if self.module_state() == 'locked': + self.log.error("Acquisition process is currently running : you can't change this parameter" + " until the acquisition is completely stopped ") + return + number_scan = int(number_scan) + if not number_scan > 0: + self.log.debug("Number of acquired scan parameter must be positive ") + return + self._number_of_scan = number_scan ############################################################################## # Trigger mode functions @@ -841,7 +811,6 @@ def trigger_mode(self): @return: (str) trigger mode (must be compared to the list) """ - self._trigger_mode = self.camera_device.get_trigger_mode() return self._trigger_mode @trigger_mode.setter @@ -851,21 +820,19 @@ def trigger_mode(self, trigger_mode): @param trigger_mode: (str) trigger mode (must be compared to the list) @return: nothing """ - if not trigger_mode in self._trigger_mode_list: + if self.module_state() == 'locked': + self.log.error("Acquisition process is currently running : you can't change this parameter" + " until the acquisition is completely stopped ") + return + if not trigger_mode in self.camera_constraints['trigger_modes']: self.log.debug("Trigger mode parameter do not match with any of available trigger " - "mode of the camera ") - break - if not isinstance(trigger_mode, str): - self.log.debug("Trigger mode parameter must be a string ") - break - if not trigger_mode == self._trigger_mode: - self.log.info("Trigger mode parameter has not be changed ") - self._trigger_mode = trigger_mode - self.camera_device.set_trigger_mode(trigger_mode) - self.log.info("Trigger mode has been set correctly ") + "mode of the camera in the camera_constraints dictionary ") + return + self.camera().set_trigger_mode(trigger_mode) + self._trigger_mode = self.camera().get_trigger_mode() ############################################################################## - # Shutter mode functions + # Shutter mode functions (optional) ############################################################################## @property @@ -874,8 +841,13 @@ def shutter_mode(self): @return: (str) shutter mode (must be compared to the list) """ - self._shutter_mode = self.camera_device.get_shutter_is_open() - return self._shutter_mode + if not self.camera_constraints['shutter_modes']: + return self.camera().get_shutter_mode() + if not self.spectro_constraints['shutter_modes']: + return self.spectrometer().get_shutter_mode() + self.log.debug("Your hardware seems to don't have any shutter available has mentioned in" + "the constraints dictionaries ") + return None @shutter_mode.setter def shutter_mode(self, shutter_mode): @@ -884,19 +856,14 @@ def shutter_mode(self, shutter_mode): @param shutter_mode: (str) shutter mode (must be compared to the list) @return: nothing """ - if not shutter_mode in self._shutter_mode_list: - self.log.debug("Shutter mode parameter do not match with any of available shutter " - "mode of the camera ") - break - if not isinstance(shutter_mode, int): - self.log.debug("Shutter open mode parameter must be an int ") - break - if not shutter_mode == self._shutter_mode: - self.log.info("Shutter mode parameter has not be changed ") - break - self._shutter_mode = shutter_mode - self.camera_device.set_shutter_is_open(shutter_mode) - self.log.info("Shutter mod has been set correctly ") + if shutter_mode in self.camera_constraints['shutter_modes']: + self.camera().set_shutter_is_open(shutter_mode) + return + if shutter_mode in self.spectro_constraints['shutter_modes']: + self.spectrometer().set_shutter_is_open(shutter_mode) + return + self.log.debug("Shutter mode parameter do not match with any of available shutter " + "mode of the camera ") ############################################################################## # Temperature functions @@ -906,10 +873,13 @@ def shutter_mode(self, shutter_mode): def cooler_status(self): """Getter method returning the cooler status if ON or OFF. - @return: (int) 1 if ON or 0 if OFF #TODO: why not use 'ON' or 'OFF' + @return: (str) cooler status """ - self._cooler_status = self.camera_device.get_cooler_status() - return self._cooler_status + if self.camera_constraints['has_cooler'] == True: + return self.camera().get_cooler_status() + self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + "the camera_constraints dictionary ") + return None @cooler_status.setter def cooler_status(self, cooler_status): @@ -918,15 +888,15 @@ def cooler_status(self, cooler_status): @param cooler_status: (bool) 1 if ON or 0 if OFF @return: nothing """ - if not isinstance(cooler_status, int): - self.log.debug("Cooler status parameter must be int ") - break - if not cooler_status == self._cooler_status: - self.log.info("Cooler status parameter has not be changed ") - break - self._cooler_ON = cooler_status - self.camera_device.set_cooler_ON(cooler_status) - self.log.info("Cooler status has been changed correctly ") + cooler_status = int(cooler_status) + if not cooler_status in [0, 1]: + self.log.debug("Cooler status parameter is not correct : it must be 1 (ON) or 0 (OFF) ") + return + if self.camera_constraints['has_cooler'] == True: + self.camera().set_cooler_status(cooler_status) + return + self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + "the camera_constraints dictionary ") @property def camera_temperature(self): @@ -934,25 +904,26 @@ def camera_temperature(self): @return: (float) temperature """ - self._camera_temperature = self.camera_device.get_temperature() - return self._camera_temperature + if self.camera_constraints['has_cooler'] == True: + return self.camera().get_temperature() + self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + "the camera_constraints dictionary ") + return None @camera_temperature.setter - def camera_temperature(self, camera_temperature): #TODO: this set the setpoint, not the temperature + def camera_temperature(self, temperature_setpoint): """Setter method returning the temperature of the camera. @param temperature: (float) temperature @return: nothing """ - if not isinstance(camera_temperature, float): - self.log.debug("Camera temperature parameter must be a float ") - break - if not camera_temperature > 0: - self.log.debug("Camera temperature parameter must be a positive number ") - break - if not self._camera_temperature == camera_temperature: - self.log.info("Camera temperature parameter has not be changed ") - break - self._camera_temperature = camera_temperature - self.camera_device.set_temperature(camera_temperature) - self.log.info("Camera temperature has been changed correctly ") \ No newline at end of file + if self.camera_constraints['has_cooler'] == True: + temperature_setpoint = float(temperature_setpoint) + if temperature_setpoint<0: + self.log.debug("Camera temperature setpoint parameter must be a positive number : the temperature unit" + "is in Kelvin ") + return + self.camera().set_temperature(temperature_setpoint) + self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + "the camera_constraints dictionary ") + return None \ No newline at end of file From e905b095465b21096de4bc88ff0e11bfc54bfb49 Mon Sep 17 00:00:00 2001 From: Adrien Date: Tue, 28 Apr 2020 16:00:55 +0200 Subject: [PATCH 23/49] Update hardware + interface change the constraints dict and some outdated names --- hardware/camera/andor/Newton_940.py | 54 ++++---- hardware/spectrometer/shamrock.py | 85 ++++++++++-- interface/camera_complete_interface.py | 69 +++++----- interface/spectrometer_complete_interface.py | 128 +++++++++++-------- 4 files changed, 209 insertions(+), 127 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 4a1da2a513..26117047c8 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -297,34 +297,39 @@ def _create_error_code(self): # secured OK - tested PV - SI OK def get_constraint(self): - """ - Returns all the fixed parameters of the hardware which can be used by the logic. + """Returns all the fixed parameters of the hardware which can be used by the logic. - @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], - 'acquisition_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], - 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], - 'shutter_mode_list' : ['CLOSE', 'OPEN'...] - 'image_size' : (512, 2048), - 'pixel_size' : (1e-4, 1e-4), - 'name' : 'Newton940' - 'preamp_gain': authorized gain value (floats)} + @return: (dict) constraint dict : { + + 'name' : (str) give the camera manufacture name (ex : 'Newton940') + + 'image_size' : (tuple) ((int) image_width, (int) image_length) give the camera image size in pixels units, + + 'pixel_size' : (tuple) ((float) pixel_width, (float) pixel_length) give the pixels size in m, + + 'read_mode_list' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), + + 'trigger_mode_list' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, + + 'has_cooler' : (bool) give if the camera has temperature controller installed, + + (optional) : let this key empty if no shutter is installed ! + 'shutter_modes' : (ndarray) [(str) shutter_mode, ..] give the shutter modes available if any + shutter is installed. - Tested : yes - SI check : yes """ constraints = { - 'read_mode_list': ['FVB', 'RANDOM_TRACK', 'IMAGE'], - 'acquisition_mode_list': 'SINGLE_SCAN', - 'trigger_mode_list': ('INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', - 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'), - 'shutter_mode_list': ('AUTO', 'OPEN', 'CLOSE'), + 'name': self.get_name(), 'image_size': self.get_image_size(), 'pixel_size': self.get_pixel_size(), - 'name': self.get_name(), - 'preamp_gain': [1, 2, 4] + 'read_modes': ['FVB', 'RANDOM_TRACK', 'IMAGE'], + 'trigger_modes': ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', + 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'], + 'internal_gains': [1, 2, 4], + 'has_cooler': True, + 'shutter_modes': ['AUTO', 'OPEN', 'CLOSE'], } - return constraints def start_acquisition(self): @@ -478,17 +483,14 @@ def set_active_tracks(self, active_tracks): Setter method setting the read mode tracks parameters of the camera. @param active_tracks: (numpy array of int32) active tracks - positions [1st track start-row, 1st track end-row, ... ] + positions [(tuple) (1st track start-row, 1st track end-row), ... ] @return: nothing tested : yes SI check : yes """ - if (active_tracks.size % 2) != 0: - self.log.error('Hardware / Newton940 / set.active_tracks :' - 'check your active tracks array : size should be even !') - return - number_of_tracks = int(len(active_tracks) / 2) + number_of_tracks = int(len(active_tracks)) + active_tracks = [item for item_tuple in active_tracks for item in item_tuple] self.dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] if self._read_mode == 'FVB': diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 05ee3af6f9..50bee7ccdc 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -59,15 +59,54 @@ class Shamrock(Base,SpectrometerInterface): SLIT_MAX_WIDTH=2500E-6 def get_constraint(self): + """Returns all the fixed parameters of the hardware which can be used by the logic. + + @return: (dict) constraint dict : { + + 'optical_parameters' : (dict) { + 'focal_length' : focal length in m, + 'angular_deviation' : angular deviation in rad, + 'focal_tilt' : focal tilt in rad} + give the optical parameters (in s.i) used to measure the wavelength dispersion of the spectrometer, + + 'gratings_info' : (list) [(tuple) (ruling, blaze), ..] give the gratings info for any gratings installed + with position corresponding to grating index, + + 'number_of_gratings' : (int) give the number of gratings installed (ex:3), + + 'wavelength_limits' : (list) [[(float) wavelength_min, (float) wavelength_max], .. ] give the list of + the wavelength limits for any gratings installed with position corresponding to grating index, + + 'available_port' : (list) [[(int) input port, ..], [(int) output port, ..]] give the available + input (1st list) and output (2nd port) ports in the spectrometer, + + 'auto_slit_installed' : (list) [[(bool) input slit installed, ..], [(bool) output slit installed, ..]] + give if the related input (1st list) and output (2nd list ) ports has motorized auto slit installed. + + (optional) : let this key empty if no shutter is installed ! + 'shutter_modes' : (list) [(str) shutter_mode, ..] give the shutter modes available if any + shutter is installed. + } + """ + optical_param = self.get_optical_parameters() + optical_param = (optical_param['focal_length'], optical_param['angular_deviation'], optical_param['focal_tilt']) + gratings_info = [(info['ruling'], info['blaze']) for info in self.get_grating_info()] number_of_gratings = self.get_number_gratings() wavelength_limits = np.array([[self.get_wavelength_limit(i)] for i in range(number_of_gratings)]) auto_slit_installed = np.array([[self.auto_slit_is_present('input',0), self.auto_slit_is_present('input',1)], [self.auto_slit_is_present('output',0), self.auto_slit_is_present('output',1)]]) - flipper_mirror_installed = np.array([self.flipper_mirror_is_present('input'), self.flipper_mirror_is_present('output')]) - constraint_dict = {'number_of_gratings':number_of_gratings, - 'wavelength_limits':wavelength_limits, - 'auto_slit_installed':auto_slit_installed, - 'flipper_mirror_installed':flipper_mirror_installed} + input_port = [i for i in range(self.flipper_mirror_is_present('input')+1)] + output_port = [j for j in range(self.flipper_mirror_is_present('output')+1)] + available_port = np.array([input_port, output_port]) + constraint_dict = { + 'optical_parameters':optical_param , + 'grating_info': gratings_info, + 'number_of_gratings': number_of_gratings, + 'wavelength_limits':wavelength_limits, + 'available_port': available_port, + 'auto_slit_installed':auto_slit_installed, + } + return constraint_dict ############################################################################## # Basic functions @@ -159,9 +198,9 @@ def get_optical_parameters(self): ct.byref(focal_length), ct.byref(angular_deviation), ct.byref(focal_tilt))) - dico['focal length (m)'] = focal_length.value - dico['angular deviation (rad)'] = angular_deviation.value*np.pi/180 - dico['focal tilt (rad)'] = focal_tilt.value*np.pi/180 + dico['focal_length'] = focal_length.value + dico['angular_deviation'] = angular_deviation.value*np.pi/180 + dico['focal_tilt'] = focal_tilt.value*np.pi/180 return dico @@ -174,7 +213,7 @@ def get_optical_parameters(self): # parameters validity is secured ############################################################################## - def get_grating(self): + def get_grating_number(self): """ Returns the current grating identification (0 to self.get_number_gratings-1) @@ -185,7 +224,7 @@ def get_grating(self): self.check(self.dll.ShamrockGetGrating(self.deviceID, ct.byref(grating))) return grating.value-1 - def set_grating(self, grating): + def set_grating_number(self, grating): """ Sets the required grating (0 to self.get_number_gratings-1) @@ -250,7 +289,7 @@ def get_grating_info(self, grating): ct.byref(home), ct.byref(offset))) dico['ruling'] = line.value*1E3 - dico['blaze wavelength (nm)'] = blaze.value + dico['blaze'] = blaze.value dico['home'] = home.value dico['offset'] = offset.value return dico @@ -673,6 +712,30 @@ def set_auto_slit_width(self, flipper, port, slit_width): self.log.error('there is no slit on this port') return + def get_input_slit_width(self): + """Getter method returning the input slit width of the current used input port. + This method is a simplification of the auto_slit_width function apply for the current configuration + """ + return self.get_auto_slit_width('input', self.get_input_port()) + + def set_input_slit_width(self, slit_width): + """Setter method setting the input slit width of the current used input port. + This method is a simplification of the auto_slit_width function apply for the current configuration + """ + return self.set_auto_slit_width('input', self.get_input_port(), slit_width) + + def get_output_slit_width(self): + """Getter method returning the output slit width of the current used output port. + This method is a simplification of the auto_slit_width function apply for the current configuration + """ + return self.get_auto_slit_width('output', self.get_output_port()) + + def set_output_slit_width(self, slit_width): + """Setter method setting the output slit width of the current used output port. + This method is a simplification of the auto_slit_width function apply for the current configuration + """ + return self.set_auto_slit_width('output', self.get_output_port(), slit_width) + def auto_slit_is_present(self, flipper, port): """ Return whether the required slit is present or not diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 0315421fa3..4400769ad5 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -33,13 +33,27 @@ class CameraInterface(metaclass=InterfaceMetaclass): def get_constraint(self): """Returns all the fixed parameters of the hardware which can be used by the logic. - @return: (dict) constraint dict : {'read_mode_list' : ['FVB', 'MULTI_TRACK'...], - 'acquistion_mode_list' : ['SINGLE_SCAN', 'MULTI_SCAN'...], - 'trigger_mode_list' : ['INTERNAL', 'EXTERNAL'...], - 'shutter_mode_list' : ['CLOSE', 'OPEN'...] - 'image_size' : (512, 2048), - 'pixiel_size' : (1e-4, 1e-4), - 'name' : 'Newton940'} + @return: (dict) constraint dict : { + + 'name' : (str) give the camera manufacture name (ex : 'Newton940') + + 'image_size' : (tuple) ((int) image_width, (int) image_length) give the camera image size in pixels units, + + 'pixel_size' : (tuple) ((float) pixel_width, (float) pixel_length) give the pixels size in m, + + 'read_modes' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), + + 'internal_gains' : (list) [(float) gain, ..] give the available internal gain which can be set + to the camera preamplifier, + + 'trigger_modes' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, + + 'has_cooler' : (bool) give if the camera has temperature controller installed, + + (optional) : let this key empty if no shutter is installed ! + 'shutter_modes' : (ndarray) [(str) shutter_mode, ..] give the shutter modes available if any + shutter is installed. + """ pass @@ -114,7 +128,8 @@ def set_active_tracks(self, active_tracks): """ Setter method setting the read mode tracks parameters of the camera. - @param active_tracks: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @param active_tracks: (ndarray) active tracks positions [((int) start row, (int) end row ), ... ] + in pixel unit. @return: nothing """ pass @@ -146,24 +161,23 @@ def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): ############################################################################## @abstract_interface_method - def get_acquisition_mode(self): - """ - Getter method returning the current acquisition mode used by the camera. + def get_gain(self): + """ Get the gain. - @return: (str) acquisition mode + @return: (float) exposure gain """ pass @abstract_interface_method - def set_acquisition_mode(self, acquisition_mode): - """Setter method setting the acquisition mode used by the camera. + def set_gain(self, gain): + """ Set the gain. + + @param camera_gain: (float) desired new gain - @param acquisition_mode: (str) acquistion mode @return: nothing """ pass - @abstract_interface_method def get_exposure_time(self): """ Get the exposure time in seconds @@ -182,24 +196,6 @@ def set_exposure_time(self, exposure_time): """ pass - @abstract_interface_method - def get_gain(self): - """ Get the gain. - - @return: (float) exposure gain - """ - pass - - @abstract_interface_method - def set_gain(self, gain): - """ Set the gain. - - @param camera_gain: (float) desired new gain - - @return: nothing - """ - pass - ############################################################################## # Trigger mode functions ############################################################################## @@ -222,8 +218,11 @@ def set_trigger_mode(self, trigger_mode): pass ############################################################################## - # Shutter mode functions + # Shutter mode function (optional) ############################################################################## + # Shutter mode function are used in logic only if the camera constraints + # dictionary has 'shutter_modes' key filled. If empty this functions will not + # be used and can be ignored. @abstract_interface_method def get_shutter_status(self): diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 51c03dea18..0d1af1a261 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -32,10 +32,32 @@ class SpectrometerInterface(metaclass=InterfaceMetaclass): def get_constraint(self): """Returns all the fixed parameters of the hardware which can be used by the logic. - @return: (dict) constraint dict : {'number_of_gratings' : 3, - 'wavelength_limits' : [[wavelength_min1, wavelength_max1], ... ], - 'auto_slit_installed' : [[front input slit, side input slit], [front output slit, side output slit]], - 'flipper_mirror_installed' : [input port, output port]} + @return: (dict) constraint dict : { + + 'optical_parameters' : (tuple) (focal_length, angular_deviation, focal_tilt) + focal_length : focal length in m + angular_deviation : angular deviation in rad + focal_tilt : focal tilt in rad + give the optical parameters (in s.i) used to measure the wavelength dispersion of the spectrometer, + + 'gratings_info' : (list) [(tuple) (ruling, blaze), ..] give the gratings info for any gratings installed + with position corresponding to grating index, + + 'number_of_gratings' : (int) give the number of gratings installed (ex:3), + + 'wavelength_limits' : (list) [[(float) wavelength_min, (float) wavelength_max], .. ] give the list of + the wavelength limits for any gratings installed with position corresponding to grating index, + + 'available_port' : (list) [[(int) input port, ..], [(int) output port, ..]] give the available + input (1st list) and output (2nd port) ports in the spectrometer, + + 'auto_slit_installed' : (list) [[(bool) input slit installed, ..], [(bool) output slit installed, ..]] + give if the related input (1st list) and output (2nd list ) ports has motorized auto slit installed. + + (optional) : let this key empty if no shutter is installed ! + 'shutter_modes' : (list) [(str) shutter_mode, ..] give the shutter modes available if any + shutter is installed. + } """ pass @@ -44,13 +66,13 @@ def get_constraint(self): ############################################################################## @abstract_interface_method - def get_grating(self): + def get_grating_number(self): """Returns the current grating identification (0 to self.get_number_gratings-1) """ pass @abstract_interface_method - def set_grating(self, grating): + def set_grating_number(self, grating): """Sets the required grating (0 to self.get_number_gratings-1) @param (int) grating: grating identification number @@ -58,24 +80,6 @@ def set_grating(self, grating): """ pass - @abstract_interface_method - def get_grating_offset(self, grating): - """Returns the grating offset (unit is motor steps) - - @param (int) grating (between 0 and number_of_gratings) - @return (int) grating offset (step) - """ - pass - - @abstract_interface_method - def set_grating_offset(self, grating, offset): - """Sets the grating offset (unit is motor step) - - @param (int) grating : grating id (0..self.get_number_gratings() - (int) offset : grating offset (step) - """ - pass - ############################################################################## # Wavelength functions ############################################################################## @@ -97,29 +101,6 @@ def set_wavelength(self, wavelength): pass - ############################################################################## - # Calibration functions - ############################################################################## - - @abstract_interface_method - def get_calibration(self): - """Returns the wavelength calibration of each pixel (m) - - @return: (ndarray) wavelength range for all the pixels of the camera - """ - pass - - @abstract_interface_method - def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): - """Returns the wavelength calibration of each pixel (m). - - @param number_of_pixels: (int) number of pixels in the horizontal direction - @param pixel_width: (float) camera pixel width - @param tracks_offset: (int) camera pixel matrix offset - @return: nothing - """ - pass - ############################################################################## # Ports and Slits functions ############################################################################## @@ -161,22 +142,59 @@ def set_output_port(self, output_port): pass @abstract_interface_method - def get_auto_slit_width(self, flipper, port): - """Returns the input slit width (um) in case of a motorized slit. + def get_input_slit_width(self): + """Returns the input slit width (um) of the current input slit. + + @return: (int) offset - slit width, unit is meter (SI) + """ + pass + + @abstract_interface_method + def set_input_slit_width(self, slit_width): + """Sets the new slit width for the current input slit. + + @param slit_width: (float) slit width unit is meter (SI) + :return: nothing + """ + pass + + @abstract_interface_method + def get_output_slit_width(self): + """Returns the output slit width (um) of the current output slit. - @param flipper: (str) within ['input', 'output'] - @param port: (int) 0 for front or 1 for side port @return: (int) offset - slit width, unit is meter (SI) """ pass @abstract_interface_method - def set_auto_slit_width(self, flipper, port, slit_width): - """Sets the new slit width for the required slit. + def set_output_slit_width(self, slit_width): + """Sets the new slit width for the current output slit. - @param flipper: (str) within ['input', 'output'] - @param port: (int) 0 for front or 1 for side port @param slit_width: (float) slit width unit is meter (SI) :return: nothing """ pass + + ############################################################################## + # Shutter mode function (optional) + ############################################################################## + # Shutter mode function are used in logic only if the spectrometer constraints + # dictionary has 'shutter_modes' key filled. If empty this functions will not + # be used and can be ignored. + + @abstract_interface_method + def get_shutter_status(self): + """Getter method returning the shutter mode. + + @return: (str) shutter mode (must be compared to the list) + """ + pass + + @abstract_interface_method + def set_shutter_status(self, shutter_mode): + """Setter method setting the shutter mode. + + @param shutter_mode: (str) shutter mode (must be compared to the list) + @return: nothing + """ + pass From e58d62d0a4701097eb3cb5d597e5eff2b4aa7584 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Tue, 28 Apr 2020 20:39:09 +0200 Subject: [PATCH 24/49] Correction after test 1 --- hardware/camera/andor/Newton_940.py | 26 ++++---- hardware/spectrometer/shamrock.py | 68 ++++++++++---------- interface/camera_complete_interface.py | 2 +- interface/spectrometer_complete_interface.py | 4 +- logic/spectrum_logic.py | 15 +++-- 5 files changed, 59 insertions(+), 56 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 26117047c8..e662aade9c 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -216,7 +216,7 @@ def on_activate(self): self.on_deactivate() else: - self._constraints = self.get_constraint() + self._constraints = self.get_constraints() nx_px, ny_px = self.get_image_size() self._width, self._height = nx_px, ny_px @@ -296,7 +296,7 @@ def _create_error_code(self): # is working # secured OK - tested PV - SI OK - def get_constraint(self): + def get_constraints(self): """Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : { @@ -307,9 +307,12 @@ def get_constraint(self): 'pixel_size' : (tuple) ((float) pixel_width, (float) pixel_length) give the pixels size in m, - 'read_mode_list' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), + 'read_modes' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), - 'trigger_mode_list' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, + 'internal_gains' : (list) [(float) gain, ..] give the available internal gain which can be set + to the camera preamplifier, + + 'trigger_modes' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, 'has_cooler' : (bool) give if the camera has temperature controller installed, @@ -326,6 +329,7 @@ def get_constraint(self): 'read_modes': ['FVB', 'RANDOM_TRACK', 'IMAGE'], 'trigger_modes': ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'], + 'acquisition_modes': ['SINGLE_SCAN'], 'internal_gains': [1, 2, 4], 'has_cooler': True, 'shutter_modes': ['AUTO', 'OPEN', 'CLOSE'], @@ -421,7 +425,7 @@ def set_read_mode(self, read_mode): :return: nothing """ - if hasattr(ReadMode, read_mode) and (read_mode in self._constraints['read_mode_list']): + if hasattr(ReadMode, read_mode) and (read_mode in self._constraints['read_modes']): n_mode = c_int(getattr(ReadMode, read_mode).value) self.check(self.dll.SetReadMode(n_mode)) else: @@ -472,11 +476,7 @@ def get_active_tracks(self): tested : yes SI check : yes """ - if self._read_mode == 'RANDOM_TRACK': - return self._active_tracks - else: - self.log.error('you are not RANDOM_TRACK read_mode') - return + return self._active_tracks def set_active_tracks(self, active_tracks): """ @@ -579,7 +579,7 @@ def set_acquisition_mode(self, acquisition_mode): """ if hasattr(AcquisitionMode, acquisition_mode) \ - and (acquisition_mode in self._constraints['acquisition_mode_list']): + and (acquisition_mode in self._constraints['acquisition_modes']): n_mode = c_int(getattr(AcquisitionMode, acquisition_mode).value) self.check(self.dll.SetAcquisitionMode(n_mode)) else: @@ -680,7 +680,7 @@ def set_trigger_mode(self, trigger_mode): SI check : yes """ if hasattr(TriggerMode, trigger_mode) \ - and (trigger_mode in self._constraints['trigger_mode_list']): + and (trigger_mode in self._constraints['trigger_modes']): n_mode = c_int(getattr(TriggerMode, trigger_mode).value) self.check(self.dll.SetTriggerMode(n_mode)) self._trigger_mode = trigger_mode @@ -718,7 +718,7 @@ def set_shutter_status(self, shutter_status): """ if hasattr(ShutterMode, shutter_status) \ - and (shutter_status in self._constraints['shutter_mode_list']): + and (shutter_status in self._constraints['shutter_modes']): mode = c_int(getattr(ShutterMode, shutter_status).value) self.check(self.dll.SetShutter(self._shutter_TTL, mode, self._shutter_closing_time, self._shutter_opening_time)) diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 50bee7ccdc..582a2e05a8 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -58,7 +58,7 @@ class Shamrock(Base,SpectrometerInterface): SLIT_MIN_WIDTH=10E-6 SLIT_MAX_WIDTH=2500E-6 - def get_constraint(self): + def get_constraints(self): """Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : { @@ -90,13 +90,13 @@ def get_constraint(self): """ optical_param = self.get_optical_parameters() optical_param = (optical_param['focal_length'], optical_param['angular_deviation'], optical_param['focal_tilt']) - gratings_info = [(info['ruling'], info['blaze']) for info in self.get_grating_info()] number_of_gratings = self.get_number_gratings() + gratings_info = [(self.get_grating_info(i)['ruling'], self.get_grating_info(i)['blaze']) for i in range(number_of_gratings)] wavelength_limits = np.array([[self.get_wavelength_limit(i)] for i in range(number_of_gratings)]) auto_slit_installed = np.array([[self.auto_slit_is_present('input',0), self.auto_slit_is_present('input',1)], [self.auto_slit_is_present('output',0), self.auto_slit_is_present('output',1)]]) - input_port = [i for i in range(self.flipper_mirror_is_present('input')+1)] - output_port = [j for j in range(self.flipper_mirror_is_present('output')+1)] + input_port = [i for i in range(self.flipper_mirror_is_present(1)+1)] + output_port = [j for j in range(self.flipper_mirror_is_present(2)+1)] available_port = np.array([input_port, output_port]) constraint_dict = { 'optical_parameters':optical_param , @@ -133,7 +133,7 @@ def on_activate(self): self.dll.ShamrockGetNumberDevices(ct.byref(nd)) #self.nd = nd.value self.deviceID = 0 #hard coding : whatever the number of devices... we work with the first. Fix me ? - self.gratingID = self.get_grating() + self.gratingID = self.get_grating_number() self.set_number_of_pixels(self._width) self.set_pixel_width(self._pixelwidth) @@ -235,13 +235,13 @@ def set_grating_number(self, grating): SI check : na """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.error('grating number is not in the validity range') + self.log.debug('grating number is not in the validity range') return if isinstance (grating, int): self.check(self.dll.ShamrockSetGrating(self.deviceID, grating+1)) else: - self.log.error('set_grating function "grating" parameter needs to be int type') + self.log.debug('set_grating function "grating" parameter needs to be int type') def get_number_gratings(self): """ @@ -279,7 +279,7 @@ def get_grating_info(self, grating): dico = {} if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.error('grating number is not in the validity range') + self.log.debug('grating number is not in the validity range') return if isinstance (grating, int): @@ -294,7 +294,7 @@ def get_grating_info(self, grating): dico['offset'] = offset.value return dico else: - self.log.error('set_grating_info function "grating" parameter needs to be int type') + self.log.debug('set_grating_info function "grating" parameter needs to be int type') def get_grating_offset(self, grating): """ @@ -308,7 +308,7 @@ def get_grating_offset(self, grating): """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.error('grating number is not in the validity range') + self.log.debug('grating number is not in the validity range') return if isinstance (grating, int): @@ -316,7 +316,7 @@ def get_grating_offset(self, grating): self.check(self.dll.ShamrockGetGratingOffset(self.deviceID, grating+1, ct.byref(grating_offset))) return grating_offset.value else: - self.log.error('get_grating_offset function "grating" parameter needs to be int type') + self.log.debug('get_grating_offset function "grating" parameter needs to be int type') def set_grating_offset(self, grating, offset): """ @@ -329,16 +329,16 @@ def set_grating_offset(self, grating, offset): """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.error('grating number is not in the validity range') + self.log.debug('grating number is not in the validity range') return if isinstance (grating, int): if isinstance(offset, int): self.check(self.dll.ShamrockSetGratingOffset(self.deviceID, grating+1, offset)) else: - self.log.error('set_grating_offset function "offset" parameter needs to be int type') + self.log.debug('set_grating_offset function "offset" parameter needs to be int type') else: - self.log.error('set_grating_offset function "grating" parameter needs to be int type') + self.log.debug('set_grating_offset function "grating" parameter needs to be int type') ############################################################################## # Wavelength functions @@ -371,10 +371,10 @@ def set_wavelength(self, wavelength): """ - minwl, maxwl = self.get_wavelength_limit(self.get_grating()) + minwl, maxwl = self.get_wavelength_limit(self.get_grating_number()) if not (minwl <= wavelength <= maxwl): - self.log.error('the wavelength you ask ({0} nm) is not in the range ' + self.log.debug('the wavelength you ask ({0} nm) is not in the range ' 'of the current grating ( [{1} ; {2}] nm)'.format(wavelength*1E9, minwl*1E9, maxwl*1E9)) return @@ -397,7 +397,7 @@ def get_wavelength_limit(self, grating): wavelength_max = ct.c_float() if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.error('grating number is not in the validity range') + self.log.debug('grating number is not in the validity range') return if isinstance (grating, int): @@ -405,7 +405,7 @@ def get_wavelength_limit(self, grating): , ct.byref(wavelength_max))) return wavelength_min.value*1E-9, wavelength_max.value*1E-9 else : - self.log.error('get_wavelength_limit function "grating" parameter needs to be int type') + self.log.debug('get_wavelength_limit function "grating" parameter needs to be int type') def set_number_of_pixels(self, number_of_pixels): """ @@ -419,7 +419,7 @@ def set_number_of_pixels(self, number_of_pixels): if isinstance (number_of_pixels, int): self.check(self.dll.ShamrockSetNumberPixels(self.deviceID, number_of_pixels)) else: - self.log.error('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') + self.log.debug('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') return def set_pixel_width(self, width): @@ -432,7 +432,7 @@ def set_pixel_width(self, width): SI check : yes """ if not (1e-6 <= width <= 100E-6): - self.log.error('the pixel width you ask ({0} um) is not in a ' + self.log.debug('the pixel width you ask ({0} um) is not in a ' 'reasonable range ( [{1} ; {2}] um)'.format(width*1E6, 1, 100)) return @@ -529,7 +529,7 @@ def set_detector_offset(self, offset): if isinstance (offset, int): self.check(self.dll.ShamrockSetDetectorOffset(self.deviceID, offset)) else : - self.log.error('set_detector_offset function "offset" parameter needs to be int type') + self.log.debug('set_detector_offset function "offset" parameter needs to be int type') ############################################################################## # Ports and Slits functions @@ -562,7 +562,7 @@ def flipper_mirror_is_present(self, flipper): if flipper in [1, 2]: self.check(self.dll.ShamrockFlipperMirrorIsPresent(self.deviceID, flipper, ct.byref(present))) else: - self.log.error('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') + self.log.debug('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') return present.value def get_input_port(self): @@ -581,7 +581,7 @@ def get_input_port(self): return input_port.value else: input_port.value=0 - self.log.error('there is no flipper mirror on input port') + self.log.info('there is no flipper mirror on input port') return input_port.value def get_output_port(self): @@ -600,7 +600,7 @@ def get_output_port(self): return output_port.value else: output_port.value=0 - self.log.error('there is no flipper mirror on output port') + self.log.info('there is no flipper mirror on output port') return output_port.value def set_input_port(self, input_port): @@ -618,9 +618,9 @@ def set_input_port(self, input_port): if input_port in [0,1] : self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 1, input_port)) else: - self.log.error('set_input_port function : input port should be 0 (front) or 1 (side)') + self.log.debug('set_input_port function : input port should be 0 (front) or 1 (side)') else: - self.log.error('there is no flipper mirror on input port') + self.log.debug('there is no flipper mirror on input port') def set_output_port(self, output_port): """ @@ -637,9 +637,9 @@ def set_output_port(self, output_port): if output_port in [0, 1]: self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 2, output_port)) else: - self.log.error('set_output_port function : output port should be 0 (front) or 1 (side)') + self.log.debug('set_output_port function : output port should be 0 (front) or 1 (side)') else: - self.log.error('there is no flipper mirror on output port') + self.log.debug('there is no flipper mirror on output port') def slit_index(self, flipper, port): """ @@ -676,7 +676,7 @@ def get_auto_slit_width(self, flipper, port): slit_index=self.slit_index(flipper, port) if slit_index==0: - self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return slit_width = ct.c_float() @@ -685,7 +685,7 @@ def get_auto_slit_width(self, flipper, port): self.check(self.dll.ShamrockGetAutoSlitWidth(self.deviceID, slit_index, ct.byref(slit_width))) return slit_width.value*1E-6 else: - self.log.error('there is no slit on this port !') + self.log.debug('there is no slit on this port !') def set_auto_slit_width(self, flipper, port, slit_width): """ @@ -700,16 +700,16 @@ def set_auto_slit_width(self, flipper, port, slit_width): """ slit_index = self.slit_index(flipper, port) if slit_index == 0: - self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] if self.auto_slit_is_present(flipper, port): if (self.SLIT_MIN_WIDTH <= slit_width <=self.SLIT_MAX_WIDTH): self.check(self.dll.ShamrockSetAutoSlitWidth(self.deviceID, slit_index, slit_width*1E6)) else: - self.log.error('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) + self.log.debug('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) else: - self.log.error('there is no slit on this port') + self.log.debug('there is no slit on this port') return def get_input_slit_width(self): @@ -748,7 +748,7 @@ def auto_slit_is_present(self, flipper, port): """ slit_index = self.slit_index(flipper, port) if slit_index == 0: - self.log.error('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return present = ct.c_int() self.check(self.dll.ShamrockAutoSlitIsPresent(self.deviceID, slit_index, ct.byref(present))) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 4400769ad5..bf1b18b3e4 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -30,7 +30,7 @@ class CameraInterface(metaclass=InterfaceMetaclass): """ @abstract_interface_method - def get_constraint(self): + def get_constraints(self): """Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : { diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 0d1af1a261..54d187288c 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -29,7 +29,7 @@ class SpectrometerInterface(metaclass=InterfaceMetaclass): """ @abstract_interface_method - def get_constraint(self): + def get_constraints(self): """Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : { @@ -182,7 +182,6 @@ def set_output_slit_width(self, slit_width): # dictionary has 'shutter_modes' key filled. If empty this functions will not # be used and can be ignored. - @abstract_interface_method def get_shutter_status(self): """Getter method returning the shutter mode. @@ -190,7 +189,6 @@ def get_shutter_status(self): """ pass - @abstract_interface_method def set_shutter_status(self, shutter_mode): """Setter method setting the shutter mode. diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 5321bb212b..7d4b660e5e 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -116,7 +116,7 @@ def on_activate(self): # QTimer for asynchronous execution : self._timer = QtCore.QTimer() - self._timer.singleShot(True) + self._timer.setSingleShot(True) self._timer.timeout.connect(self.loop_acquisition) self._loop_counter = 0 @@ -246,6 +246,9 @@ def grating_number(self): """Getter method returning the grating number used by the spectrometer. @return: (int) active grating number + + Tested : yes + SI check : yes """ return self._grating_number @@ -256,13 +259,16 @@ def grating_number(self, grating_number): @param grating_number: (int) gating number to set active @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" " until the acquisition is completely stopped ") return grating_number = int(grating_number) - if not 0 < grating_number < self._number_of_gratings: + if not 0 < grating_number < self.spectro_constraints['number_of_gratings']: self.log.debug('Grating number parameter is not correct : it must be in range 0 to {} ' .format(self.spectro_constraints['number_of_gratings'] - 1)) return @@ -293,8 +299,7 @@ def center_wavelength(self, wavelength): " until the acquisition is completely stopped ") return wavelength = float(wavelength) - wavelength_min = self.spectro_constraints['wavelength_limits'][self._grating_number, 0] - wavelength_max = self.spectro_constraints['wavelength_limits'][self._grating_number, 1] + wavelength_min, wavelength_max = self.spectro_constraints['wavelength_limits'][self._grating_number] if not wavelength_min < wavelength < wavelength_max: self.log.debug('Wavelength parameter is not correct : it must be in range {} to {} ' .format(wavelength_min, wavelength_max)) @@ -312,7 +317,7 @@ def wavelength_spectrum(self): image_length = self.camera_constraints['image_size'][1] pixel_length = self.camera_constraints['pixel_size'][1] pixels_vector = np.arange(-image_length//2, image_length//2 - image_length%2)*pixel_length - focal_length, angular_dev, focal_tilt = self.spectro_constraints['optical_parameters']['focal_length'] + focal_length, angular_dev, focal_tilt = self.spectro_constraints['optical_parameters'] ruling, blaze = self.spectro_constraints['gratings_info'][self._grating_number] wavelength_spectrum = pixels_vector/np.sqrt(focal_length**2+pixels_vector**2)/ruling + self._center_wavelength return wavelength_spectrum From 6d2c7b8406abe2bc0d126b4f46f638151ee48c98 Mon Sep 17 00:00:00 2001 From: Pierre Valvin Date: Thu, 30 Apr 2020 02:05:59 +0200 Subject: [PATCH 25/49] Update after test --- hardware/camera/andor/Newton_940.py | 20 +- hardware/spectrometer/shamrock.py | 88 +++--- interface/spectrometer_complete_interface.py | 2 + logic/spectrum_logic.py | 290 ++++++++++++++----- 4 files changed, 290 insertions(+), 110 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index e662aade9c..a18c6136d1 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -218,7 +218,7 @@ def on_activate(self): else: self._constraints = self.get_constraints() nx_px, ny_px = self.get_image_size() - self._width, self._height = nx_px, ny_px + self._height, self._width = nx_px, ny_px self.set_cooler_status(self._cooler_status) self.set_temperature(self._temperature) @@ -309,6 +309,8 @@ def get_constraints(self): 'read_modes' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), + 'readout_speed' : (list) + 'internal_gains' : (list) [(float) gain, ..] give the available internal gain which can be set to the camera preamplifier, @@ -322,15 +324,19 @@ def get_constraints(self): """ + internal_gains = [gain for gain in GAIN_DICT.values()] + readout_speeds = [speed for speed in READOUT_SPEED_DICT.values()] + constraints = { 'name': self.get_name(), 'image_size': self.get_image_size(), 'pixel_size': self.get_pixel_size(), 'read_modes': ['FVB', 'RANDOM_TRACK', 'IMAGE'], + 'readout_speeds': readout_speeds, 'trigger_modes': ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'], 'acquisition_modes': ['SINGLE_SCAN'], - 'internal_gains': [1, 2, 4], + 'internal_gains': internal_gains, 'has_cooler': True, 'shutter_modes': ['AUTO', 'OPEN', 'CLOSE'], } @@ -435,7 +441,7 @@ def set_read_mode(self, read_mode): self._read_mode = read_mode if read_mode == 'IMAGE': - self.set_active_image(1, 1, 1, self._width, 1, self._height) + self.set_active_image(1, 1, 1, self._height, 1, self._width,) elif read_mode == 'RANDOM_TRACK': self.set_active_tracks(self._active_tracks) @@ -515,10 +521,10 @@ def get_active_image(self): tested : yes SI check : yes """ - active_image_parameters = [self._hbin, self._vbin, self._hstart, self._hend, self._vstart, self._vend] + active_image_parameters = [self._vbin, self._hbin, self._vstart, self._vend, self._hstart, self._hend] return active_image_parameters - def set_active_image(self, hbin, vbin, hstart, hend, vstart, vend): + def set_active_image(self, vbin, hbin, vstart, vend, hstart, hend): """ Setter method setting the read mode image parameters of the camera. @@ -823,7 +829,7 @@ def get_image_size(self): nx_px = ct.c_int() ny_px = ct.c_int() self.check(self.dll.GetDetector(byref(nx_px), byref(ny_px))) - return nx_px.value, ny_px.value + return ny_px.value, nx_px.value def get_pixel_size(self): """ @@ -834,7 +840,7 @@ def get_pixel_size(self): x_px = ct.c_float() y_px = ct.c_float() self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) - return x_px.value * 1E-6, y_px.value * 1E-6 + return y_px.value * 1E-6, x_px.value * 1E-6 def get_ready_state(self): """ diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 582a2e05a8..97cdb0399e 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -92,7 +92,7 @@ def get_constraints(self): optical_param = (optical_param['focal_length'], optical_param['angular_deviation'], optical_param['focal_tilt']) number_of_gratings = self.get_number_gratings() gratings_info = [(self.get_grating_info(i)['ruling'], self.get_grating_info(i)['blaze']) for i in range(number_of_gratings)] - wavelength_limits = np.array([[self.get_wavelength_limit(i)] for i in range(number_of_gratings)]) + wavelength_limits = np.array([self.get_wavelength_limit(i) for i in range(number_of_gratings)]) auto_slit_installed = np.array([[self.auto_slit_is_present('input',0), self.auto_slit_is_present('input',1)], [self.auto_slit_is_present('output',0), self.auto_slit_is_present('output',1)]]) input_port = [i for i in range(self.flipper_mirror_is_present(1)+1)] @@ -100,7 +100,7 @@ def get_constraints(self): available_port = np.array([input_port, output_port]) constraint_dict = { 'optical_parameters':optical_param , - 'grating_info': gratings_info, + 'gratings_info': gratings_info, 'number_of_gratings': number_of_gratings, 'wavelength_limits':wavelength_limits, 'available_port': available_port, @@ -235,13 +235,13 @@ def set_grating_number(self, grating): SI check : na """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.debug('grating number is not in the validity range') + self.log.warning('grating number is not in the validity range') return if isinstance (grating, int): self.check(self.dll.ShamrockSetGrating(self.deviceID, grating+1)) else: - self.log.debug('set_grating function "grating" parameter needs to be int type') + self.log.warning('set_grating function "grating" parameter needs to be int type') def get_number_gratings(self): """ @@ -279,7 +279,7 @@ def get_grating_info(self, grating): dico = {} if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.debug('grating number is not in the validity range') + self.log.warning('grating number is not in the validity range') return if isinstance (grating, int): @@ -294,7 +294,7 @@ def get_grating_info(self, grating): dico['offset'] = offset.value return dico else: - self.log.debug('set_grating_info function "grating" parameter needs to be int type') + self.log.warning('set_grating_info function "grating" parameter needs to be int type') def get_grating_offset(self, grating): """ @@ -308,7 +308,7 @@ def get_grating_offset(self, grating): """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.debug('grating number is not in the validity range') + self.log.warning('grating number is not in the validity range') return if isinstance (grating, int): @@ -316,7 +316,7 @@ def get_grating_offset(self, grating): self.check(self.dll.ShamrockGetGratingOffset(self.deviceID, grating+1, ct.byref(grating_offset))) return grating_offset.value else: - self.log.debug('get_grating_offset function "grating" parameter needs to be int type') + self.log.warning('get_grating_offset function "grating" parameter needs to be int type') def set_grating_offset(self, grating, offset): """ @@ -329,16 +329,16 @@ def set_grating_offset(self, grating, offset): """ if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.debug('grating number is not in the validity range') + self.log.warning('grating number is not in the validity range') return if isinstance (grating, int): if isinstance(offset, int): self.check(self.dll.ShamrockSetGratingOffset(self.deviceID, grating+1, offset)) else: - self.log.debug('set_grating_offset function "offset" parameter needs to be int type') + self.log.warning('set_grating_offset function "offset" parameter needs to be int type') else: - self.log.debug('set_grating_offset function "grating" parameter needs to be int type') + self.log.warning('set_grating_offset function "grating" parameter needs to be int type') ############################################################################## # Wavelength functions @@ -374,7 +374,7 @@ def set_wavelength(self, wavelength): minwl, maxwl = self.get_wavelength_limit(self.get_grating_number()) if not (minwl <= wavelength <= maxwl): - self.log.debug('the wavelength you ask ({0} nm) is not in the range ' + self.log.warning('the wavelength you ask ({0} nm) is not in the range ' 'of the current grating ( [{1} ; {2}] nm)'.format(wavelength*1E9, minwl*1E9, maxwl*1E9)) return @@ -397,15 +397,15 @@ def get_wavelength_limit(self, grating): wavelength_max = ct.c_float() if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.debug('grating number is not in the validity range') - return + self.log.warning('grating number is not in the validity range') + return 1 - if isinstance (grating, int): - self.check(self.dll.ShamrockGetWavelengthLimits(self.deviceID, grating+1, ct.byref(wavelength_min) - , ct.byref(wavelength_max))) - return wavelength_min.value*1E-9, wavelength_max.value*1E-9 - else : - self.log.debug('get_wavelength_limit function "grating" parameter needs to be int type') + if not isinstance(grating, int): + self.log.warning('get_wavelength_limit function "grating" parameter needs to be int type') + return 2 + self.check(self.dll.ShamrockGetWavelengthLimits(self.deviceID, grating + 1, ct.byref(wavelength_min) + , ct.byref(wavelength_max))) + return wavelength_min.value * 1E-9, wavelength_max.value * 1E-9 def set_number_of_pixels(self, number_of_pixels): """ @@ -419,7 +419,7 @@ def set_number_of_pixels(self, number_of_pixels): if isinstance (number_of_pixels, int): self.check(self.dll.ShamrockSetNumberPixels(self.deviceID, number_of_pixels)) else: - self.log.debug('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') + self.log.warning('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') return def set_pixel_width(self, width): @@ -432,7 +432,7 @@ def set_pixel_width(self, width): SI check : yes """ if not (1e-6 <= width <= 100E-6): - self.log.debug('the pixel width you ask ({0} um) is not in a ' + self.log.warning('the pixel width you ask ({0} um) is not in a ' 'reasonable range ( [{1} ; {2}] um)'.format(width*1E6, 1, 100)) return @@ -529,7 +529,7 @@ def set_detector_offset(self, offset): if isinstance (offset, int): self.check(self.dll.ShamrockSetDetectorOffset(self.deviceID, offset)) else : - self.log.debug('set_detector_offset function "offset" parameter needs to be int type') + self.log.warning('set_detector_offset function "offset" parameter needs to be int type') ############################################################################## # Ports and Slits functions @@ -562,7 +562,7 @@ def flipper_mirror_is_present(self, flipper): if flipper in [1, 2]: self.check(self.dll.ShamrockFlipperMirrorIsPresent(self.deviceID, flipper, ct.byref(present))) else: - self.log.debug('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') + self.log.warning('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') return present.value def get_input_port(self): @@ -618,9 +618,9 @@ def set_input_port(self, input_port): if input_port in [0,1] : self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 1, input_port)) else: - self.log.debug('set_input_port function : input port should be 0 (front) or 1 (side)') + self.log.warning('set_input_port function : input port should be 0 (front) or 1 (side)') else: - self.log.debug('there is no flipper mirror on input port') + self.log.warning('there is no flipper mirror on input port') def set_output_port(self, output_port): """ @@ -637,9 +637,9 @@ def set_output_port(self, output_port): if output_port in [0, 1]: self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 2, output_port)) else: - self.log.debug('set_output_port function : output port should be 0 (front) or 1 (side)') + self.log.warning('set_output_port function : output port should be 0 (front) or 1 (side)') else: - self.log.debug('there is no flipper mirror on output port') + self.log.warning('there is no flipper mirror on output port') def slit_index(self, flipper, port): """ @@ -676,7 +676,7 @@ def get_auto_slit_width(self, flipper, port): slit_index=self.slit_index(flipper, port) if slit_index==0: - self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return slit_width = ct.c_float() @@ -685,7 +685,7 @@ def get_auto_slit_width(self, flipper, port): self.check(self.dll.ShamrockGetAutoSlitWidth(self.deviceID, slit_index, ct.byref(slit_width))) return slit_width.value*1E-6 else: - self.log.debug('there is no slit on this port !') + self.log.warning('there is no slit on this {} port !'.format(flipper)) def set_auto_slit_width(self, flipper, port, slit_width): """ @@ -700,16 +700,16 @@ def set_auto_slit_width(self, flipper, port, slit_width): """ slit_index = self.slit_index(flipper, port) if slit_index == 0: - self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] if self.auto_slit_is_present(flipper, port): if (self.SLIT_MIN_WIDTH <= slit_width <=self.SLIT_MAX_WIDTH): self.check(self.dll.ShamrockSetAutoSlitWidth(self.deviceID, slit_index, slit_width*1E6)) else: - self.log.debug('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) + self.log.warning('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) else: - self.log.debug('there is no slit on this port') + self.log.warning('there is no slit on this port') return def get_input_slit_width(self): @@ -748,7 +748,7 @@ def auto_slit_is_present(self, flipper, port): """ slit_index = self.slit_index(flipper, port) if slit_index == 0: - self.log.debug('slit parameters are not valid. parameter should be within ([input, output],[0,1]') + self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') return present = ct.c_int() self.check(self.dll.ShamrockAutoSlitIsPresent(self.deviceID, slit_index, ct.byref(present))) @@ -790,7 +790,27 @@ def shamrock_grating_is_present(self): self.check(self.dll.ShamrockGratingIsPresent(self.deviceID, ct.byref(present))) return present.value + ############################################################################## + # Shutter mode function (optional) + ############################################################################## + # Shutter mode function are used in logic only if the spectrometer constraints + # dictionary has 'shutter_modes' key filled. If empty this functions will not + # be used and can be ignored. + + def get_shutter_status(self): + """Getter method returning the shutter mode. + + @return: (str) shutter mode (must be compared to the list) + """ + pass + + def set_shutter_status(self, shutter_mode): + """Setter method setting the shutter mode. + @param shutter_mode: (str) shutter mode (must be compared to the list) + @return: nothing + """ + pass diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 54d187288c..dd4879bb38 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -182,6 +182,7 @@ def set_output_slit_width(self, slit_width): # dictionary has 'shutter_modes' key filled. If empty this functions will not # be used and can be ignored. + @abstract_interface_method def get_shutter_status(self): """Getter method returning the shutter mode. @@ -189,6 +190,7 @@ def get_shutter_status(self): """ pass + @abstract_interface_method def set_shutter_status(self, shutter_mode): """Setter method setting the shutter mode. diff --git a/logic/spectrum_logic.py b/logic/spectrum_logic.py index 7d4b660e5e..84c67df42a 100644 --- a/logic/spectrum_logic.py +++ b/logic/spectrum_logic.py @@ -75,6 +75,7 @@ def __init__(self, **kwargs): @param dict kwargs: optional parameters """ super().__init__(**kwargs) + self.threadlock = Mutex() def on_activate(self): """ Initialisation performed during activation of the module. @@ -101,6 +102,11 @@ def on_activate(self): self._output_slit_width = self.spectrometer().get_output_slit_width() # read mode : + if 'shutter_modes' in self.camera_constraints: + self._shutter_mode = self.camera().get_shutter_status() + if 'shutter_modes' in self.spectro_constraints: + self._shutter_mode = self.spectrometer().get_shutter_status() + self._read_mode = self.camera().get_read_mode() self._active_tracks = self.camera().get_active_tracks() self._active_image = self.camera().get_active_image() @@ -152,23 +158,35 @@ def loop_acquisition(self): """ Method acquiring data by using the camera hardware method 'start_acquisition'. This method is connected to a timer signal : after timer start this slot is called with a period of a time delay. After a certain number of call this method can stop the timer if not in 'LIVE' acquisition. + + Tested : yes + SI check : yes """ self.camera().start_acquisition() - self._acquired_data = self.get_acquired_data() + # Get acquired data : new scan if 'LIVE' or concatenate scan if 'MULTI' + if self._shutter_mode[-4:] == 'LIVE': + self._acquired_data = self.get_acquired_data() + else: + self._acquired_data = np.append(self._acquired_data, self.get_acquired_data()) + # If 'MULTI' stop acquisition after number_of_scan*number_accumulation loop + if self._acquisition_mode[-10:-5] == 'MULTI' and self._loop_counter%self._number_of_scan == 0\ + and self._loop_counter!=0: + self._timer.stop() + self.module_state.unlock() + self.log.info("Loop acquisition finished : module state is 'idle' ") + return + else: + delay_time = self._scan_delay + # Accumulation mode starting with 'ACC' : if accumulation finished apply cosmic rejection from this last data if self._acquisition_mode[:3] == 'ACC': - if self._loop_counter%self._number_accumulated_scan == 0: + if self._loop_counter%self._number_accumulated_scan == 0 and self._loop_counter!=0: data = self._acquired_data[-self._number_accumulated_scan] np.delete(self._acquired_data, np.s_[-self._number_accumulated_scan], axis=0) - self._acquired_data = self.reject_cosmic(data) + self._acquired_data = np.append(self._acquired_data, self.reject_cosmic(data)) delay_time = self._scan_delay else: delay_time = self._accumulation_delay - if self._acquisition_mode[-10:-5] == 'MULTI' and self._loop_counter%self._number_of_scan == 0: - self._timer.stop() - self.module_state.unlock() - self.log.info("Acquisition finished : module state is 'idle' ") - else: - delay_time = self._scan_delay + # Callback the loop function after delay time self._timer.start(delay_time) @@ -178,6 +196,8 @@ def reject_cosmic(self, data): parameters. The rejection is carry out with a mask rejecting values outside their standard deviation with a weight given by a coeff coeff_rej_cosmic. This method should be only used in "accumulation mode". + Tested : yes + SI check : yes """ if len(data) 0: - self.log.debug("Exposure time parameter must be a positive number ") + self.log.warning("Exposure time parameter must be a positive number ") return self.camera().set_exposure_time(exposure_time) self._exposure_time = self.camera().get_exposure_time() @@ -703,6 +805,9 @@ def accumulation_delay(self): """Getter method returning the accumulation delay between consecutive scan during accumulate acquisition mode. @return: (float) accumulation delay + + Tested : yes + SI check : yes """ return self._accumulation_delay @@ -712,6 +817,9 @@ def accumulation_delay(self, accumulation_delay): @param accumulation_delay: (float) accumulation delay @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" @@ -719,10 +827,10 @@ def accumulation_delay(self, accumulation_delay): return accumulation_delay = float(accumulation_delay) if not accumulation_delay > 0 : - self.log.debug("Accumulation delay parameter must be a positive number ") + self.log.warning("Accumulation delay parameter must be a positive number ") return if not self._exposure_time < accumulation_delay < self._scan_delay: - self.log.debug("Accumulation delay parameter must be a value between" + self.log.warning("Accumulation delay parameter must be a value between" "the current exposure time and scan delay values ") return self._accumulation_delay = accumulation_delay @@ -732,6 +840,9 @@ def scan_delay(self): """Getter method returning the scan delay between consecutive scan during multiple acquisition mode. @return: (float) scan delay + + Tested : yes + SI check : yes """ return self._scan_delay @@ -741,6 +852,9 @@ def scan_delay(self, scan_delay): @param scan_delay: (float) scan delay @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" @@ -748,10 +862,10 @@ def scan_delay(self, scan_delay): return scan_delay = float(scan_delay) if not scan_delay > 0: - self.log.debug("Scan delay parameter must be a positive number ") + self.log.warning("Scan delay parameter must be a positive number ") return if not self._exposure_time < self._scan_delay: - self.log.debug("Scan delay parameter must be a value bigger than" + self.log.warning("Scan delay parameter must be a value bigger than" "the current exposure time ") return self._scan_delay = scan_delay @@ -761,6 +875,9 @@ def number_accumulated_scan(self): """Getter method returning the number of accumulated scan during accumulate acquisition mode. @return: (int) number of accumulated scan + + Tested : yes + SI check : yes """ return self._number_accumulated_scan @@ -770,6 +887,9 @@ def number_accumulated_scan(self, number_scan): @param number_scan: (int) number of accumulated scan @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" @@ -777,7 +897,7 @@ def number_accumulated_scan(self, number_scan): return number_scan = int(number_scan) if not number_scan > 0: - self.log.debug("Number of accumulated scan parameter must be positive ") + self.log.warning("Number of accumulated scan parameter must be positive ") return self._number_accumulated_scan = number_scan @@ -786,6 +906,9 @@ def number_of_scan(self): """Getter method returning the number of acquired scan during multiple acquisition mode. @return: (int) number of acquired scan + + Tested : yes + SI check : yes """ return self._number_of_scan @@ -795,6 +918,9 @@ def number_of_scan(self, number_scan): @param number_scan: (int) number of acquired scan @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" @@ -802,7 +928,7 @@ def number_of_scan(self, number_scan): return number_scan = int(number_scan) if not number_scan > 0: - self.log.debug("Number of acquired scan parameter must be positive ") + self.log.warning("Number of acquired scan parameter must be positive ") return self._number_of_scan = number_scan @@ -815,6 +941,9 @@ def trigger_mode(self): """Getter method returning the current trigger mode used by the camera. @return: (str) trigger mode (must be compared to the list) + + Tested : yes + SI check : yes """ return self._trigger_mode @@ -824,13 +953,16 @@ def trigger_mode(self, trigger_mode): @param trigger_mode: (str) trigger mode (must be compared to the list) @return: nothing + + Tested : yes + SI check : yes """ if self.module_state() == 'locked': self.log.error("Acquisition process is currently running : you can't change this parameter" " until the acquisition is completely stopped ") return if not trigger_mode in self.camera_constraints['trigger_modes']: - self.log.debug("Trigger mode parameter do not match with any of available trigger " + self.log.warning("Trigger mode parameter do not match with any of available trigger " "mode of the camera in the camera_constraints dictionary ") return self.camera().set_trigger_mode(trigger_mode) @@ -845,12 +977,15 @@ def shutter_mode(self): """Getter method returning the shutter mode. @return: (str) shutter mode (must be compared to the list) + + Tested : yes + SI check : yes """ - if not self.camera_constraints['shutter_modes']: - return self.camera().get_shutter_mode() - if not self.spectro_constraints['shutter_modes']: - return self.spectrometer().get_shutter_mode() - self.log.debug("Your hardware seems to don't have any shutter available has mentioned in" + if 'shutter_modes' in self.camera_constraints: + return self._shutter_mode + if 'shutter_modes' in self.spectro_constraints: + return self._shutter_mode + self.log.warning("Your hardware seems to don't have any shutter available has mentioned in" "the constraints dictionaries ") return None @@ -860,15 +995,20 @@ def shutter_mode(self, shutter_mode): @param shutter_mode: (str) shutter mode (must be compared to the list) @return: nothing + + Tested : yes + SI check : yes """ - if shutter_mode in self.camera_constraints['shutter_modes']: - self.camera().set_shutter_is_open(shutter_mode) + if 'shutter_modes' in self.camera_constraints: + self.camera().set_shutter_status(shutter_mode) + self._shutter_mode = self.camera().get_shutter_status() return - if shutter_mode in self.spectro_constraints['shutter_modes']: - self.spectrometer().set_shutter_is_open(shutter_mode) + if 'shutter_modes' in self.spectro_constraints: + self.spectrometer().set_shutter_status(shutter_mode) + self._shutter_mode = self.spectrometer().get_shutter_status() return - self.log.debug("Shutter mode parameter do not match with any of available shutter " - "mode of the camera ") + self.log.warning("Shutter mode parameter do not match with any of available shutter " + "mode of the camera ") ############################################################################## # Temperature functions @@ -879,10 +1019,13 @@ def cooler_status(self): """Getter method returning the cooler status if ON or OFF. @return: (str) cooler status + + Tested : yes + SI check : yes """ if self.camera_constraints['has_cooler'] == True: return self.camera().get_cooler_status() - self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + self.log.warning("Your camera hardware seems to don't have any temperature controller set as mentioned in" "the camera_constraints dictionary ") return None @@ -892,15 +1035,18 @@ def cooler_status(self, cooler_status): @param cooler_status: (bool) 1 if ON or 0 if OFF @return: nothing + + Tested : yes + SI check : yes """ cooler_status = int(cooler_status) if not cooler_status in [0, 1]: - self.log.debug("Cooler status parameter is not correct : it must be 1 (ON) or 0 (OFF) ") + self.log.warning("Cooler status parameter is not correct : it must be 1 (ON) or 0 (OFF) ") return if self.camera_constraints['has_cooler'] == True: self.camera().set_cooler_status(cooler_status) return - self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + self.log.warning("Your camera hardware seems to don't have any temperature controller set as mentioned in" "the camera_constraints dictionary ") @property @@ -908,10 +1054,13 @@ def camera_temperature(self): """Getter method returning the temperature of the camera. @return: (float) temperature + + Tested : yes + SI check : yes """ if self.camera_constraints['has_cooler'] == True: return self.camera().get_temperature() - self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + self.log.warning("Your camera hardware seems to don't have any temperature controller set as mentioned in" "the camera_constraints dictionary ") return None @@ -921,14 +1070,17 @@ def camera_temperature(self, temperature_setpoint): @param temperature: (float) temperature @return: nothing + + Tested : yes + SI check : yes """ if self.camera_constraints['has_cooler'] == True: temperature_setpoint = float(temperature_setpoint) if temperature_setpoint<0: - self.log.debug("Camera temperature setpoint parameter must be a positive number : the temperature unit" + self.log.warning("Camera temperature setpoint parameter must be a positive number : the temperature unit" "is in Kelvin ") return self.camera().set_temperature(temperature_setpoint) - self.log.debug("Your camera hardware seems to don't have any temperature controller set as mentioned in" + self.log.warning("Your camera hardware seems to don't have any temperature controller set as mentioned in" "the camera_constraints dictionary ") return None \ No newline at end of file From 021d531f363139958687ebf376cf97a7a25dadf2 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 30 Apr 2020 16:10:50 +0200 Subject: [PATCH 26/49] first pass at andor camera hardware --- hardware/camera/andor/Newton_940.py | 562 ++++++++-------------- hardware/camera/andor/errorcodes_newton.h | 72 --- 2 files changed, 190 insertions(+), 444 deletions(-) delete mode 100644 hardware/camera/andor/errorcodes_newton.h diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index a18c6136d1..bc181e12a3 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -36,12 +36,14 @@ from core.util.modules import get_main_dir import os +# Bellow are the classes used by Andor dll. They are not par of Qudi interfaces + class ReadMode(Enum): - # We leave here all the possible read modes, but SINGLE TRACK and MULTI TRACK are considered - # as particular cases of RANDOM TRACK. They are not included in the constraint dictionary - # and could be removed here. + """ Class defining the possible read mode supported by Andor dll + Only FVB, RANDOM_TRACK and IMAGE are used by this module. + """ FVB = 0 MULTI_TRACK = 1 RANDOM_TRACK = 2 @@ -50,10 +52,10 @@ class ReadMode(Enum): class AcquisitionMode(Enum): - # We leave here all the possible acquisition modes, but we considere that ACCUMULATE and KINETICS - # have to be done in the logic stage. FAST_KINETICS and RUN_TILL_ABORT are not implemented and - # therefore, they are not included in the constraint dictionary + """ Class defining the possible acquisition mode supported by Andor dll + Only SINGLE_SCAN is used by this module. + """ SINGLE_SCAN = 1 ACCUMULATE = 2 KINETICS = 3 @@ -62,6 +64,7 @@ class AcquisitionMode(Enum): class TriggerMode(Enum): + """ Class defining the possible trigger mode supported by Andor dll """ INTERNAL = 0 EXTERNAL = 1 EXTERNAL_START = 6 @@ -71,22 +74,14 @@ class TriggerMode(Enum): class ShutterMode(Enum): + """ Class defining the possible shutter mode supported by Andor dll """ AUTO = 0 OPEN = 1 CLOSE = 2 -GAIN_DICT = { - 0: 1, # index=0 - gain is 1x - 1: 2, # index=1 - gain is 2x - 2: 4 # ... -} +OK_CODE = 20002 -READOUT_SPEED_DICT = { - 0: 50000, # index=0 - Horizontal shift is 50kHz - 1: 1000000, # index=1 - Horizontal shift is 1MHz - 2: 3000000 # ... -} ERROR_DICT = { 20001: "DRV_ERROR_CODES", @@ -131,34 +126,31 @@ class ShutterMode(Enum): } -class Newton940(Base, CameraInterface): - """ - Hardware class for Andor Newton940 CCD spectroscopy cameras - """ - _modtype = 'camera' - _modclass = 'hardware' +class Newton940(Base, CameraInterface): # Todo : rename class for any Andor camera + """ Hardware class for Andor Newton940 CCD spectroscopy cameras """ + + _dll_location = ConfigOption('dll_location', missing='error') + _close_shutter_on_deactivate = ConfigOption('close_shutter_on_deactivate', False) _default_cooler_status = ConfigOption('default_cooler_status', True) _default_temperature = ConfigOption('default_temperature', 260) - _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') - _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') - _default_readout_speed = ConfigOption('default_readout_speed', 50000) - _default_preamp_gain = ConfigOption('default_preamp_gain', 1) + _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') #todo: remove + _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') #todo: remove + _default_readout_speed = ConfigOption('default_readout_speed', 50000) #todo: remove + _default_preamp_gain = ConfigOption('default_preamp_gain', 1) #todo: remove _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') - _default_exposure = ConfigOption('default_exposure', 1.0) - _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') - _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) - _default_binning = ConfigOption('default_binning', [1, 1]) - _default_ROI = ConfigOption('default_ROI', [1, 2048, 1, 512]) - _default_max_exposure_time = ConfigOption('default_max_exposure_time', 600) + _default_exposure = ConfigOption('default_exposure', 1.0) #todo: remove + _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') #todo: remove, but maybe close on deactivate ? + _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) #todo: remove + _default_binning = ConfigOption('default_binning', [1, 1]) #todo: remove + _default_ROI = ConfigOption('default_ROI', [1, 2048, 1, 512]) #todo: remove + _default_max_exposure_time = ConfigOption('default_max_exposure_time', 600) #todo: does this come from the dll and why forbid it ? - _dll_location = ConfigOption('dll_location', missing='error') - - _camera_name = 'Newton940' + _camera_name = 'Newton940'#todo: from config option or read from dll ? _cooler_status = _default_cooler_status _temperature = _default_temperature - _max_cooling = -85 + _max_cooling = -85 # todo _acquisition_mode = _default_acquisition_mode _read_mode = _default_read_mode _readout_speed = _default_readout_speed @@ -192,9 +184,9 @@ class Newton940(Base, CameraInterface): _vstart = 1 _vend = 2 - _constraints = {} - _min_temperature = 189 - _max_temperature = 262 + _constraints = {} # not todo: this is nice ! + _min_temperature = 189 #todo: why ? + _max_temperature = 262 # todo: why ? ############################################################################## # Basic module activation/deactivation @@ -205,99 +197,70 @@ class Newton940(Base, CameraInterface): def on_activate(self): """ Initialization performed during activation of the module. """ + try: + self.dll = ct.cdll.LoadLibrary(self._dll_location) + except OSError: + self.log.error('Error during dll loading of the Andor camera, check the dll path.') - self.dll = ct.cdll.LoadLibrary(self._dll_location) - self.error_code = self._create_error_code() - - error_code = self.dll.Initialize() - - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.info('Problem during camera (Andor/Newton) initialization') - self.on_deactivate() + status_code = self.dll.Initialize() + if status_code != OK_CODE: + self.log.error('Problem during camera (Andor/Newton) initialization') + return - else: - self._constraints = self.get_constraints() - nx_px, ny_px = self.get_image_size() - self._height, self._width = nx_px, ny_px + self._constraints = self.get_constraints() + self._height, self._width = self.get_image_size() - self.set_cooler_status(self._cooler_status) - self.set_temperature(self._temperature) + self.set_cooler_status(self._cooler_status) + self.set_temperature(self._temperature) - self.set_acquisition_mode(self._acquisition_mode) - self.set_read_mode(self._read_mode) - self.set_readout_speed(self._readout_speed) - self.set_gain(self._preamp_gain) - self.set_trigger_mode(self._trigger_mode) + self.set_acquisition_mode(self._acquisition_mode) #todo: done by logic + self.set_read_mode(self._read_mode) #todo: done by logic + self.set_readout_speed(self._readout_speed) #todo: done by logic + self.set_gain(self._preamp_gain) #todo: done by logic + self.set_trigger_mode(self._trigger_mode) - self.set_exposure_time(self._exposure) + self.set_exposure_time(self._exposure) #todo: done by logic - self.set_shutter_status(self._shutter_status) + self.set_shutter_status(self._shutter_status) - self._active_tracks = np.array(self._default_active_tracks) - self._hbin = self._binning[0] - self._vbin = self._binning[1] - self._hstart = self._ROI[0] - self._hend = self._ROI[1] - self._vstart = self._ROI[2] - self._vend = self._ROI[3] + self._active_tracks = np.array(self._default_active_tracks) + self._hbin = self._binning[0] + self._vbin = self._binning[1] + self._hstart = self._ROI[0] + self._hend = self._ROI[1] + self._vstart = self._ROI[2] + self._vend = self._ROI[3] def on_deactivate(self): - """ - De-initialisation performed during deactivation of the module. - - """ + """ De-initialisation performed during deactivation of the module. """ if not (self.get_ready_state()): self.stop_acquisition() - self.set_shutter_status('CLOSE') - self.check(self.dll.ShutDown()) + if self._close_shutter_on_deactivate: + self.set_shutter_status('CLOSE') #todo: closed ? + try: + self.dll.ShutDown() + except: + self.log.warning('Error while shutting down Andor camera via dll.') ############################################################################## # Error management ############################################################################## - # is working - # secured OK - tested PV - SI OK - def check(self, func_val): """ Check routine for the received error codes. :return: the dll function error code Tested : no """ - if not func_val == 20002: - self.log.error('Error in Newton with error_code {0}:\n' - '{1}'.format(func_val, self.error_code[func_val])) + if not func_val == OK_CODE: + self.log.error('Error in Newton with error_code {0}:' + '{1}'.format(func_val, ERROR_DICT[func_val])) return func_val - def _create_error_code(self): - """ Create a dictionary with the error_code for the device. - """ - main_dir = get_main_dir() - content = [] - - filename = os.path.join(main_dir, 'hardware', 'camera', 'andor', 'errorcodes_newton.h') - try: - with open(filename) as f: - content = f.readlines() - except: - self.log.error('No file "errorcodes_newton.h" could be found in the ' - 'hardware/camera/andor/ directory!') - - error_code = {} - for line in content: - if '#define ' in line: - error_string, error_value = line.split()[-2:] - error_code[int(error_value)] = error_string - - return error_code - ############################################################################## # Basic functions ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_constraints(self): - """Returns all the fixed parameters of the hardware which can be used by the logic. + """ Returns all the fixed parameters of the hardware which can be used by the logic. @return: (dict) constraint dict : { @@ -323,9 +286,10 @@ def get_constraints(self): shutter is installed. """ + #todo: there is many thing, a class would probably be preferable - internal_gains = [gain for gain in GAIN_DICT.values()] - readout_speeds = [speed for speed in READOUT_SPEED_DICT.values()] + internal_gains = [1, 2, 4] # todo : read from hardware + readout_speeds = [50000, 1000000, 3000000] # todo : read from hardware constraints = { 'name': self.get_name(), @@ -343,179 +307,114 @@ def get_constraints(self): return constraints def start_acquisition(self): - """ - Starts the acquisition - :return: nothing - Tested : yes - SI check : yes - """ + """ Starts the acquisition """ self.check(self.dll.StartAcquisition()) - self.dll.WaitForAcquisition() - return + self.dll.WaitForAcquisition() # todo: this is not synchronous def stop_acquisition(self): - """ - Stops/aborts the acquisition - @return nothing - tested : yes - SI check : yes - """ + """ Aborts the acquisition """ self.check(self.dll.AbortAcquisition()) - return def get_acquired_data(self): - """ Return an array of last acquired image. + """ Return the last acquired data. @return numpy array: image data in format [[row],[row]...] - - tested : yes, but without graphic tool... that may highlight more problems - SI check : yes """ - dim = 0 - h = 0 - - if self._acquisition_mode == 'SINGLE_SCAN': # for those who would like to add more acquisition modes - if self._read_mode == 'FVB': - dim = self._width - h = 1 - - if self._read_mode == 'RANDOM_TRACK': - dim = self._width * self._number_of_tracks - h = self._number_of_tracks - - if self._read_mode == 'IMAGE': - dim = self._width * self._height - h = self._height - - dim = int(dim) - image_array = np.zeros(dim) - c_image_array = c_int * dim - c_image = c_image_array() - - error_code = self.dll.GetAcquiredData(pointer(c_image), dim) - - if ERROR_DICT[error_code] != 'DRV_SUCCESS': - self.log.warning('Could not retrieve an image. {0}'.format(ERROR_DICT[error_code])) - - else: - self.log.debug('image length {0}'.format(len(c_image))) - for i in range(len(c_image)): - image_array[i] = c_image[i] - - image_array = np.reshape(image_array, (self._width, h)) + if self._read_mode == 'FVB': + height = 1 + if self._read_mode == 'RANDOM_TRACK': + height = self._number_of_tracks + if self._read_mode == 'IMAGE': + height = self._height + dim = int(self._width * height) + image_array = np.zeros(dim) + c_image_array = c_int * dim + c_image = c_image_array() + + status_code = self.dll.GetAcquiredData(pointer(c_image), dim) + + if status_code != OK_CODE: + self.log.warning('Could not retrieve an image. {0}'.format(ERROR_DICT[status_code])) + else: + for i in range(len(c_image)): #todo: there must be something better here + image_array[i] = c_image[i] - return image_array + return np.reshape(image_array, (self._width, height)) ############################################################################## # Read mode functions ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_read_mode(self): - """ - Getter method returning the current read mode used by the camera. + """ Getter method returning the current read mode used by the camera. - :return: @str read mode - tested : yes - SI check : yes + @return (str): read mode """ - return self._read_mode def set_read_mode(self, read_mode): - """ - Setter method setting the read mode used by the camera. + """ Setter method setting the read mode used by the camera. - :param read_mode: @str read mode among those defined in the self.get_constraint - :return: nothing + @param (str) read_mode: read mode among those defined in the self.get_constraint """ if hasattr(ReadMode, read_mode) and (read_mode in self._constraints['read_modes']): n_mode = c_int(getattr(ReadMode, read_mode).value) self.check(self.dll.SetReadMode(n_mode)) else: - self.log.warning('HW/Newton940/set_read_mode() : read_mode not supported') + self.log.error('read_mode not supported') return self._read_mode = read_mode if read_mode == 'IMAGE': - self.set_active_image(1, 1, 1, self._height, 1, self._width,) + self.set_active_image(1, 1, 1, self._height, 1, self._width) elif read_mode == 'RANDOM_TRACK': self.set_active_tracks(self._active_tracks) - return - def get_readout_speed(self): - """ - :return: @float : the readout_speed (Horizontal shift) in Hz - tested : yes - SI check : yes + """ Get the current readout speed (in Hz) + + @return (float): the readout_speed (Horizontal shift) in Hz """ return self._readout_speed def set_readout_speed(self, readout_speed): - """ + """ Set the readout speed (in Hz) - :param readout_speed: @float Horizontal shift in Hz - :return: nothing - tested : yes - SI check : yes + @param (float) readout_speed: horizontal shift in Hz """ - if readout_speed in list(READOUT_SPEED_DICT.values()): - readout_speed_index = list(READOUT_SPEED_DICT.values()).index(readout_speed) + if readout_speed in self._constraints['readout_speeds']: + readout_speed_index = self._constraints['readout_speeds'].index(readout_speed) self.check(self.dll.SetHSSpeed(0, readout_speed_index)) self._readout_speed = readout_speed - return else: - self.log.warning('Hardware / Newton940 / set.readout_speed : readout_speed value is not available, ' - 'please check self.get_constraints') - return + self.log.error('Readout_speed value error, value {} is not in correct.'.format(readout_speed)) def get_active_tracks(self): - """ - Getter method returning the read mode tracks parameters of the camera. + """ Getter method returning the read mode tracks parameters of the camera. - @return: (np array) active tracks positions [1st track start-row, 1st track end-row, ... ] - tested : yes - SI check : yes + @return (list): active tracks positions [(start_1, end_1), (start_2, end_2), ... ] """ return self._active_tracks def set_active_tracks(self, active_tracks): - """ - Setter method setting the read mode tracks parameters of the camera. + """ Setter method for the active tracks of the camera. - @param active_tracks: (numpy array of int32) active tracks - positions [(tuple) (1st track start-row, 1st track end-row), ... ] - @return: nothing - tested : yes - SI check : yes + @param (list) active_tracks: active tracks positions as [(start_1, end_1), (start_2, end_2), ... ] """ - + if self._read_mode != 'RANDOM_TRACK': + self.log.error('Active tracks are defined outside of RANDOM_TRACK mode.') + return number_of_tracks = int(len(active_tracks)) - active_tracks = [item for item_tuple in active_tracks for item in item_tuple] + active_tracks = [item for item_tuple in active_tracks for item in item_tuple] #todo: decompose this, do not use imbricated loops in one line loop self.dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] - - if self._read_mode == 'FVB': - self.log.error('Hardware / Newton940 / set.active_tracks : ' - 'you want to define acquisition track, but current read_mode is FVB') - elif self._read_mode == 'IMAGE': - self.log.error('Hardware / Newton940 / set.active_tracks :' - 'you want to define acquisition track, but current read_mode is IMAGE') - elif self._read_mode == 'RANDOM_TRACK': - self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) - + self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) self._active_tracks = active_tracks self._number_of_tracks = number_of_tracks - return - def get_active_image(self): - """ - Getter method returning the read mode image parameters of the camera. + """ Getter method returning the read mode image parameters of the camera. @return: (np array) active image parameters [hbin, vbin, hstart, hend, vstart, vend] tested : yes @@ -525,8 +424,7 @@ def get_active_image(self): return active_image_parameters def set_active_image(self, vbin, hbin, vstart, vend, hstart, hend): - """ - Setter method setting the read mode image parameters of the camera. + """ Setter method setting the read mode image parameters of the camera. @param hbin: (int) horizontal pixel binning @param vbin: (int) vertical pixel binning @@ -541,8 +439,8 @@ def set_active_image(self, vbin, hbin, vstart, vend, hstart, hend): hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin), c_int(hstart), c_int(hend),\ c_int(vstart), c_int(vend) - error_code = self.check(self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend)) - if ERROR_DICT[error_code] == 'DRV_SUCCESS': + status_code = self.check(self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend)) + if status_code == OK_CODE: self._hbin = hbin.value self._vbin = vbin.value self._hstart = hstart.value @@ -554,34 +452,24 @@ def set_active_image(self, vbin, hbin, vstart, vend, hstart, hend): self._ROI = (self._hstart, self._hend, self._vstart, self._vend) self._binning = (self._hbin, self._vbin) else: - self.log.error('Hardware / Newton940 / set_active_image :' - 'Call to the function went wrong:{0}'.format(ERROR_DICT[error_code])) + self.log.error('Call to set_active_image went wrong:{0}'.format(ERROR_DICT[status_code])) return ############################################################################## # Acquisition mode functions ############################################################################## - # is working - # secured OK - tested PV - SI OK def get_acquisition_mode(self): - """ - Getter method returning the current acquisition mode used by the camera. + """ Getter method returning the current acquisition mode used by the camera. - :return: @str acquisition mode (must be compared to a dict) - tested : yes - SI check : yes + @return (str): acquisition mode """ return self._acquisition_mode def set_acquisition_mode(self, acquisition_mode): - """ - Setter method setting the acquisition mode used by the camera. + """ Setter method setting the acquisition mode used by the camera. - :param acquisition_mode: @str read mode (must be compared to a dict) - :return: nothing - tested : yes - SI check : yes + @param acquisition_mode: @str read mode (must be compared to a dict) """ if hasattr(AcquisitionMode, acquisition_mode) \ @@ -589,101 +477,71 @@ def set_acquisition_mode(self, acquisition_mode): n_mode = c_int(getattr(AcquisitionMode, acquisition_mode).value) self.check(self.dll.SetAcquisitionMode(n_mode)) else: - self.log.warning('HW/Newton940/set_acquisition_mode() : ' - '{0} mode is not supported'.format(acquisition_mode)) + self.log.error('{} mode is not supported'.format(acquisition_mode)) return self._acquisition_mode = acquisition_mode return def get_exposure_time(self): - """ - Get the exposure time in seconds + """ Get the exposure time in seconds - @return float exposure time in s - tested : yes - SI check : yes + @return (float) : exposure time in s """ + return self._get_acquisition_timings['exposure'] - exposure = c_float() - accumulate = c_float() - kinetic = c_float() - self.check(self.dll.GetAcquisitionTimings(byref(exposure), byref(accumulate), byref(kinetic))) - self._exposure = exposure.value + def _get_acquisition_timings(self): + """ Get the acquisitions timings from the dll - return self._exposure + @return (dict): dict containing keys 'exposure', 'accumulate', 'kinetic' and their values in seconds """ + exposure, accumulate, kinetic = c_float(), c_float(), c_float() + self.check(self.dll.GetAcquisitionTimings(byref(exposure), byref(accumulate), byref(kinetic))) + return {'exposure': exposure.value, 'accumulate': accumulate.value, 'kinetic': kinetic.value} def set_exposure_time(self, exposure_time): """ Set the exposure time in seconds - @param float exposure_time: desired new exposure time - - @return float: new exposure time - tested : yes - SI check : yes + @param (float) exposure_time: desired new exposure time """ if exposure_time < 0: - self.log.error('HW/Newton940/set_exposure_time() : exposure_time is negative !!') + self.log.error('Exposure_time can not be negative.') return - elif exposure_time > self._max_exposure_time: - self.log.error('HW/Newton940/set_exposure_time() : ' - 'exposure time is above the high limit : {0}'.format(self._max_exposure_time)) + if exposure_time > self._max_exposure_time: + self.log.error('Exposure time is above the high limit : {0} s'.format(self._max_exposure_time)) return - self.check(self.dll.SetExposureTime(c_float(exposure_time))) - self._exposure = exposure_time def get_gain(self): - """ - Get the gain + """ Get the gain - @return float: exposure gain - tested : yes - SI check : yes + @return (float): exposure gain """ - return self._preamp_gain + return self._preamp_gain #todo: read from hardware ? def set_gain(self, gain): """ Set the gain - @param float gain: desired new gain - - @return float: new exposure gain - tested : yes - SI check : yes + @param (float) gain: desired new gain """ - gain = int(gain) - if gain in list(GAIN_DICT.values()): - gain_index = list(GAIN_DICT.values()).index(gain) - self.check(self.dll.SetPreAmpGain(gain_index)) - self._preamp_gain = gain + if gain not in self._constraints['internal_gains']: + self.log.error('gain value {} is not available.'.format(gain)) return - else: - self.log.warning('HW/Newton940/set_gain() : gain value is not available') + gain_index = self._constraints['internal_gains'].index(gain) + self.check(self.dll.SetPreAmpGain(gain_index)) ############################################################################## # Trigger mode functions ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_trigger_mode(self): - """ - Getter method returning the current trigger mode used by the camera. + """ Getter method returning the current trigger mode used by the camera. - :return: @str trigger mode (must be compared to a dict) - tested : yes - SI check : yes + @return (str): current trigger mode """ - return self._trigger_mode + return self._trigger_mode #todo: read from hardware ? def set_trigger_mode(self, trigger_mode): - """ - Setter method setting the trigger mode used by the camera. + """ Setter method for the trigger mode used by the camera. - :param trigger_mode: @str trigger mode (must be compared to a dict) - :return: nothing - tested : yes - SI check : yes + @param (str) trigger_mode: trigger mode (must be compared to a dict) """ if hasattr(TriggerMode, trigger_mode) \ and (trigger_mode in self._constraints['trigger_modes']): @@ -691,8 +549,7 @@ def set_trigger_mode(self, trigger_mode): self.check(self.dll.SetTriggerMode(n_mode)) self._trigger_mode = trigger_mode else: - self.log.warning('HW/Newton940/set_trigger_mode() : ' - '{0} mode is not supported'.format(trigger_mode)) + self.log.warning('Trigger mode {} is not supported.'.format(trigger_mode)) return self._trigger_mode = trigger_mode return @@ -700,27 +557,19 @@ def set_trigger_mode(self, trigger_mode): ############################################################################## # Shutter mode functions ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_shutter_status(self): - """ - Getter method returning if the shutter is open. + """ Getter method returning if the shutter is open. - :return: @bool shutter open ? + @return (bool): @bool shutter open ? #todo: status tested : yes SI check : yes """ - return self._shutter_status + return self._shutter_status #todo from hardware ? def set_shutter_status(self, shutter_status): - """ - Setter method setting if the shutter is open. + """ Setter method for the shutter state. - :param shutter_status: @string - :return: nothing - tested : yes - SI check : yes + @param (str): shutter_status """ if hasattr(ShutterMode, shutter_status) \ @@ -739,78 +588,56 @@ def set_shutter_status(self, shutter_status): ############################################################################## # Temperature functions ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_cooler_status(self): - """ - Getter method returning the cooler status if ON or OFF. + """ Getter method returning the cooler status if ON or OFF. - :return: @bool True if ON or False if OFF or 0 if error - tested : yes - SI check : yes + @return (bool): True if ON or False if OFF or 0 if error """ - return self._cooler_status + return self._cooler_status #todo: from harware def set_cooler_status(self, cooler_status): - """ - Setter method returning the cooler status if ON or OFF. + """ Setter method for the cooler status. - :cooler_ON: @bool True if ON or False if OFF - :return: nothing - tested : yes - SI check : yes + @param (bool) cooler_status: True if ON or False if OFF """ if cooler_status: self.check(self.dll.CoolerON()) - self._cooler_status = True + self._cooler_status = True #todo: handled by camera else: self.check(self.dll.CoolerOFF()) - self._cooler_status = False - return + self._cooler_status = False #todo: handled by camera def get_temperature(self): - """ - Getter method returning the temperature of the camera. + """ Getter method returning the temperature of the camera. - :return: @float temperature (K) or 0 if error - tested : yes - SI check : yes + @return (float): temperature (in Kelvin) """ temp = c_int32() self.dll.GetTemperature(byref(temp)) - return temp.value + 273.15 def set_temperature(self, temperature): - """ - Getter method returning the temperature of the camera. + """ Setter method for the the temperature setpoint of the camera. - :param temperature: @float temperature (K) or 0 if error - :return: nothing - tested : yes - SI check : yes + @param (float) temperature: temperature (in Kelvin) """ - temperature = int(temperature) + temperature = int(temperature) #todo: conversion to integer might mess things up, this has do ne checked nicely if self._min_temperature < temperature < self._max_temperature: temperature = int(temperature-273.15) self.check(self.dll.SetTemperature(temperature)) self._temperature = temperature+273.15 else: - self.log.warning('HW/Newton940/set_temperature() : temperature is not in the validity range ') + self.log.warning('Temperature {} Kelvin is not in the validity range.') - return + #todo: setpoint getter ? ############################################################################## # Internal functions, for constraints preparation ############################################################################## - # is working - # secured OK - tested PV - SI OK - def get_name(self): - """ - :return: string local camera name with serial number + """ Get a name for the camera + @return (str): local camera name with serial number """ serial = ct.c_int() self.check(self.dll.GetCameraSerialNumber(byref(serial))) @@ -818,13 +645,9 @@ def get_name(self): return name def get_image_size(self): - """ - Returns the sensor size in pixels (x;y) - - :return: tuple (nw_px, ny_px) : int number of pixel along x and y axis + """ Returns the sensor size in pixels (width, height) - Tested : yes - SI check : yes + @return tuple(int, int): number of pixel in width and height """ nx_px = ct.c_int() ny_px = ct.c_int() @@ -832,35 +655,30 @@ def get_image_size(self): return ny_px.value, nx_px.value def get_pixel_size(self): - """ - :return: tuple (float) (x,y) pixel size unit is meter - tested : yes - SI : yes + """ Get the physical pixel size (width, height) in meter + + @return tuple(float, float): physical pixel size in meter """ x_px = ct.c_float() y_px = ct.c_float() self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) - return y_px.value * 1E-6, x_px.value * 1E-6 + return y_px.value * 1e-6, x_px.value * 1e-6 def get_ready_state(self): - """ - :return: Bool =True if camera state is idle - tested : yes - SI : yes + """ Get the state of the camera to know if the acquisition is finished or not yet. + + @return (bool): True if camera state is idle """ code = ct.c_int() self.check(self.dll.GetStatus(byref(code))) - if ERROR_DICT[code.value] != 'DRV_SUCCESS': - return True - else: - return False + return code.value == OK_CODE - def get_current_config(self): - """ - :return: dictionary with camera current configuration. - please see also get_constraints() + def _get_current_config(self): + """ Internal helper method to get the camera parameters in a printable dict. + + @return (dict): dictionary with camera current configuration. """ - config = { + config = { #todo use getters for most of them 'camera ID..................................': self.get_name(), 'sensor size (pixels).......................': self.get_image_size(), 'pixel size (m)............................': self.get_pixel_size(), diff --git a/hardware/camera/andor/errorcodes_newton.h b/hardware/camera/andor/errorcodes_newton.h deleted file mode 100644 index d6325cfd63..0000000000 --- a/hardware/camera/andor/errorcodes_newton.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - -This file contains the header file for the error code of the Andor-Shamrock device. - -Qudi 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. - -Qudi 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 Qudi. If not, see . - -Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the -top-level directory of this distribution and at - -*/ - - -/* -Taken from: - -*/ - - -#define DRV_ERROR_CODES 20001 -#define DRV_SUCCESS 20002 -#define DRV_VXNOTINSTALLED 20003 -#define DRV_ERROR_FILELOAD 20006 -#define DRV_ERROR_VXD_INIT 20007 -#define DRV_ERROR_PAGELOCK 20010 -#define DRV_ERROR_PAGE_UNLOCK 20011 -#define DRV_ERROR_ACK 20013 -#define DRV_NO_NEW_DATA 20024 -#define DRV_SPOOLERROR 20026 -#define DRV_TEMP_OFF 20034 -#define DRV_TEMP_NOT_STABILIZED 20035 -#define DRV_TEMP_STABILIZED 20036 -#define DRV_TEMP_NOT_REACHED 20037 -#define DRV_TEMP_OUT_RANGE 20038 -#define DRV_TEMP_NOT_SUPPORTED 20039 -#define DRV_TEMP_DRIFT 20040 -#define DRV_COF_NOTLOADED 20050 -#define DRV_FLEXERROR 20053 -#define DRV_P1INVALID 20066 -#define DRV_P2INVALID 20067 -#define DRV_P3INVALID 20068 -#define DRV_P4INVALID 20069 -#define DRV_INIERROR 20070 -#define DRV_COERROR 20071 -#define DRV_ACQUIRING 20072 -#define DRV_IDLE 20073 -#define DRV_TEMPCYCLE 20074 -#define DRV_NOT_INITIALIZED 20075 -#define DRV_P5INVALID 20076 -#define DRV_P6INVALID 20077 -#define P7_INVALID 20083 -#define DRV_USBERROR 20089 -#define DRV_NOT_SUPPORTED 20091 -#define DRV_INVALID_TRIGGER_MODE 20095 -#define DRV_BINNING_ERROR 20099 -#define DRV_NOCAMERA 20990 -#define DRV_NOT_SUPPORTED 20991 -#define DRV_NOT_AVAILABLE 20992 - - - - From 542596866516365bf35c4edbfe4ac26ec58261dc Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 30 Apr 2020 17:30:35 +0200 Subject: [PATCH 27/49] changed camera interface to proposed solution --- interface/camera_complete_interface.py | 265 +++++++++++++++---------- 1 file changed, 156 insertions(+), 109 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index bf1b18b3e4..f20d71806d 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -This file contains the updated Qudi Interface for a camera. +This file contains the updated Qudi Interface for a scientific camera used for spectroscopy. Qudi is free software: you can redistribute it and/or modify @@ -20,164 +20,206 @@ Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the top-level directory of this distribution and at """ +from enum import Enum from core.interface import abstract_interface_method from core.meta import InterfaceMetaclass +class ReadMode(Enum): + """ Class defining the possible read modes of the camera + + 'FVB': Full vertical binning. Returns the signal integrated over the whole columns of pixels, giving one curve. + 'MULTIPLE_TRACK': The signal is integrated over one or multiple tracks, giving a lost of one or multiple curves. + 'IMAGE': The camera return the signal over all the pixels as a 2d array. + 'IMAGE_ADVANCED': The camera return the signal over the super pixels on a given region of the camera, as a 2d array. + """ + FVB = 0 + MULTIPLE_TRACKS = 1 + IMAGE = 2 + IMAGE_ADVANCED = 3 + + +class Constraints: + """ Class defining formally the hardware constraints """ + def __init__(self): + self.name = '', # Camera manufacture name (ex : 'Newton940') + self.image_size = (None, None), # Camera size in pixels (width, height) + self.pixel_size = (None, None), # Physical size of the pixels in meter (width, height) + self.read_modes = [], # Read mode supported by the camera (see ReadMode class) + self.internal_gains = [], # Internal gains supported by the camera (list of float) + self.readout_speeds = [], # Readout speed supported by the camera, in Hz (list of float) + self.has_cooler = False, # Tells if the camera has a cooling system + self.has_shutter = False, # Tells if the camera has a shutter + self.trigger_modes = [], # User defined trigger mode (list of string) + + +class ImageAdvancedParameters: + """ Class defining formally a binning and a region of the camera for IMAGE_ADVANCED mode """ + def __init__(self): + self.horizontal_binning = 1, + self.vertical_binning = 1, + self.horizontal_start = 0, + self.horizontal_end = None, # Has to be an integer + self.vertical_start = 0, + self.vertical_end = None # Has to be an integer + + class CameraInterface(metaclass=InterfaceMetaclass): - """ This interface is used to manage and visualize a simple camera - """ + """ This interface is used to manage a camera used for spectroscopy """ @abstract_interface_method def get_constraints(self): - """Returns all the fixed parameters of the hardware which can be used by the logic. - - @return: (dict) constraint dict : { - - 'name' : (str) give the camera manufacture name (ex : 'Newton940') - - 'image_size' : (tuple) ((int) image_width, (int) image_length) give the camera image size in pixels units, - - 'pixel_size' : (tuple) ((float) pixel_width, (float) pixel_length) give the pixels size in m, - - 'read_modes' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), - - 'internal_gains' : (list) [(float) gain, ..] give the available internal gain which can be set - to the camera preamplifier, - - 'trigger_modes' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, - - 'has_cooler' : (bool) give if the camera has temperature controller installed, - - (optional) : let this key empty if no shutter is installed ! - 'shutter_modes' : (ndarray) [(str) shutter_mode, ..] give the shutter modes available if any - shutter is installed. + """ Returns all the fixed parameters of the hardware which can be used by the logic. + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware """ pass ############################################################################## # Basic functions ############################################################################## - @abstract_interface_method def start_acquisition(self): - """ Start a single acquisition - - @return: nothing - """ + """ Starts an acquisition of the current mode and returns immediately """ pass @abstract_interface_method def stop_acquisition(self): - """ Stop/abort live or single acquisition - - @return: nothing - """ + """ Abort acquisition """ pass @abstract_interface_method def get_acquired_data(self): - """ Return an array of last acquired image. + """ Return an array of last acquired data. + + @return: Data in the format depending on the read mode. - @return: (ndarray) image data in format [[row],[row]...] - Each pixel might be a float, integer or sub pixels + Depending on the read mode, the format is : + 'FVB' : 1d array + 'MULTIPLE_TRACKS' : list of 1d arrays + 'IMAGE' 2d array of shape (width, height) + 'IMAGE_ADVANCED' 2d array of shape (width, height) + + Each value might be a float or an integer. """ pass ############################################################################## # Read mode functions ############################################################################## - @abstract_interface_method def get_read_mode(self): - """Getter method returning the current read mode used by the camera. + """ Getter method returning the current read mode used by the camera. - @return: (str) read mode + @return (ReadMode): Current read mode """ pass @abstract_interface_method - def set_read_mode(self, read_mode): - """Setter method setting the read mode used by the camera. + def set_read_mode(self, value): + """ Setter method setting the read mode used by the camera. - @param read_mode: (str) read mode - @return: nothing + @param (ReadMode) value: read mode to set """ pass + ############################################################################## + # Readout speed functions + ############################################################################## @abstract_interface_method def get_readout_speed(self): + """ Get the current readout speed of the camera + + This value is one of the possible values given by constraints + """ pass @abstract_interface_method - def set_readout_speed(self, readout_speed): + def set_readout_speed(self, value): + """ Set the readout speed of the camera + + @param (float) value: Readout speed to set, must be a value from the constraints readout_speeds list + """ pass + ############################################################################## + # Active tracks functions + # + # Method used only for read mode MULTIPLE_TRACKS + ############################################################################## @abstract_interface_method def get_active_tracks(self): - """Getter method returning the read mode tracks parameters of the camera. + """ Getter method returning the read mode tracks parameters of the camera. - @return: (ndarray) active tracks positions [1st track start, 1st track end, ... ] + @return (list): active tracks positions [(start_1, end_1), (start_2, end_2), ... ] + + Should only be used while in MULTIPLE_TRACKS mode """ pass @abstract_interface_method - def set_active_tracks(self, active_tracks): - """ - Setter method setting the read mode tracks parameters of the camera. + def set_active_tracks(self, value): + """ Setter method for the active tracks of the camera. - @param active_tracks: (ndarray) active tracks positions [((int) start row, (int) end row ), ... ] - in pixel unit. - @return: nothing + @param (list) value: active tracks positions as [(start_1, end_1), (start_2, end_2), ... ] + + Some camera can sum the signal over tracks of pixels (all width times a height given by start and stop pixels) + This sum is done internally before the analog to digital converter to reduce the signal noise. + + Should only be used while in MULTIPLE_TRACKS mode """ pass + ############################################################################## + # Image advanced functions + # + # Method used only for read mode IMAGE_ADVANCED + ############################################################################## @abstract_interface_method - def get_active_image(self): - """Getter method returning the read mode image parameters of the camera. + def get_image_advanced_parameters(self): + """ Getter method returning the image parameters of the camera. + + @return (ImageAdvancedParameters): Current image advanced parameters - @return: (ndarray) active image parameters [hbin, vbin, hstart, hend, vstart, vend] + Should only be used while in IMAGE_ADVANCED mode """ pass @abstract_interface_method - def set_active_image(self,hbin, vbin, hstart, hend, vstart, vend): - """Setter method setting the read mode image parameters of the camera. - - @param hbin: (int) horizontal pixel binning - @param vbin: (int) vertical pixel binning - @param hstart: (int) image starting row - @param hend: (int) image ending row - @param vstart: (int) image starting column - @param vend: (int) image ending column - @return: nothing + def set_image_advanced_parameters(self, value): + """ Setter method setting the read mode image parameters of the camera. + + @param (ImageAdvancedParameters) value: Parameters to set + + Should only be used while in IMAGE_ADVANCED mode """ pass ############################################################################## - # Acquisition mode functions + # Gain mode functions ############################################################################## - @abstract_interface_method def get_gain(self): - """ Get the gain. + """ Get the current gain. - @return: (float) exposure gain + @return (float): Current gain + + Gain value should be one in the constraints internal_gains list. """ pass @abstract_interface_method - def set_gain(self, gain): + def set_gain(self, value): """ Set the gain. - @param camera_gain: (float) desired new gain - - @return: nothing + @param (float) value: New gain, value should be one in the constraints internal_gains list. """ pass + ############################################################################## + # Exposure functions + ############################################################################## @abstract_interface_method def get_exposure_time(self): """ Get the exposure time in seconds @@ -187,10 +229,10 @@ def get_exposure_time(self): pass @abstract_interface_method - def set_exposure_time(self, exposure_time): + def set_exposure_time(self, value): """ Set the exposure time in seconds. - @param exposure_time: (float) desired new exposure time + @param value: (float) desired new exposure time @return: nothing """ @@ -202,79 +244,84 @@ def set_exposure_time(self, exposure_time): @abstract_interface_method def get_trigger_mode(self): - """Getter method returning the current trigger mode used by the camera. + """ Getter method returning the current trigger mode used by the camera. - @return: (str) trigger mode (must be compared to the list) + @return (str): Trigger mode + + This string should match one in the constraints trigger_modes list. """ pass @abstract_interface_method - def set_trigger_mode(self, trigger_mode): - """Setter method setting the trigger mode used by the camera. + def set_trigger_mode(self, value): + """ Setter method for the trigger mode used by the camera. - @param trigger_mode: (str) trigger mode (must be compared to the list) - @return: nothing + @param (str) value: trigger mode, should match one in the constraints trigger_modes list. """ pass ############################################################################## - # Shutter mode function (optional) + # Shutter mode function + # + # Method used only if constraints.has_shutter ############################################################################## - # Shutter mode function are used in logic only if the camera constraints - # dictionary has 'shutter_modes' key filled. If empty this functions will not - # be used and can be ignored. - @abstract_interface_method - def get_shutter_status(self): - """Getter method returning the shutter mode. + def get_shutter_open_state(self): + """ Getter method returning the shutter mode. - @return: (str) shutter mode (must be compared to the list) + @return (bool): True if the shutter is open, False of closed """ pass @abstract_interface_method - def set_shutter_status(self, shutter_mode): - """Setter method setting the shutter mode. + def set_shutter_open_state(self, value): + """ Setter method setting the shutter mode. - @param shutter_mode: (str) shutter mode (must be compared to the list) - @return: nothing + @param (bool) value: True to open, False tp close """ pass ############################################################################## # Temperature functions + # + # Method used only if constraints.has_cooler ############################################################################## - @abstract_interface_method - def get_cooler_status(self): - """Getter method returning the cooler status if ON or OFF. + def get_cooler_on(self): + """ Getter method returning the cooler status - @return: (int) 1 if ON or 0 if OFF + @return (bool): True if the cooler is on """ pass @abstract_interface_method - def set_cooler_status(self, cooler_status): - """Getter method returning the cooler status if ON or OFF. + def set_cooler_on(self, value): + """ Setter method for the the cooler status - @param cooler_status: (bool) 1 if ON or 0 if OFF - @return: nothing + @param (bool) value: True to turn it on, False to turn it off """ pass @abstract_interface_method def get_temperature(self): - """Getter method returning the temperature of the camera. + """ Getter method returning the temperature of the camera in Kelvin. - @return: (float) temperature + @return (float) : Measured temperature in kelvin """ pass @abstract_interface_method - def set_temperature(self, temperature): - """Getter method returning the temperature of the camera. + def get_temperature_setpoint(self): + """ Getter method for the temperature setpoint of the camera. - @param temperature: (float) temperature - @return: nothing + @return (float): Current setpoint in Kelvin """ - pass \ No newline at end of file + pass + + @abstract_interface_method + def set_temperature_setpoint(self, value): + """ Setter method for the temperature setpoint of the camera. + + @param (float) value: New setpoint in Kelvin + """ + pass From 1e3a447c5925b29f1c606e93ecf59d64be3ffee3 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 30 Apr 2020 19:11:52 +0200 Subject: [PATCH 28/49] started working on newton hardware new version --- hardware/camera/andor/Newton_940.py | 367 ++++++++++++---------------- 1 file changed, 154 insertions(+), 213 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index bc181e12a3..62715e8b5e 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -25,24 +25,21 @@ """ from enum import Enum -from ctypes import * import numpy as np import ctypes as ct from core.module import Base from core.configoption import ConfigOption -from interface.camera_complete_interface import CameraInterface -from core.util.modules import get_main_dir -import os - -# Bellow are the classes used by Andor dll. They are not par of Qudi interfaces +from interface.camera_complete_interface import CameraInterface, ReadMode, Constraints, ImageAdvancedParameters -class ReadMode(Enum): - """ Class defining the possible read mode supported by Andor dll +# Bellow are the classes used by Andor dll. They are not par of Qudi interfaces +class ReadModeDLL(Enum): + """ Class defining the possible read mode supported by Andor DLL - Only FVB, RANDOM_TRACK and IMAGE are used by this module. + This read mode is different from the class of the interface, be careful! + Only FVB, RANDOM_TRACK and IMAGE are used by this module. """ FVB = 0 MULTI_TRACK = 1 @@ -52,7 +49,7 @@ class ReadMode(Enum): class AcquisitionMode(Enum): - """ Class defining the possible acquisition mode supported by Andor dll + """ Class defining the possible acquisition mode supported by Andor DLL Only SINGLE_SCAN is used by this module. """ @@ -64,7 +61,7 @@ class AcquisitionMode(Enum): class TriggerMode(Enum): - """ Class defining the possible trigger mode supported by Andor dll """ + """ Class defining the possible trigger mode supported by Andor DLL """ INTERNAL = 0 EXTERNAL = 1 EXTERNAL_START = 6 @@ -74,15 +71,15 @@ class TriggerMode(Enum): class ShutterMode(Enum): - """ Class defining the possible shutter mode supported by Andor dll """ + """ Class defining the possible shutter mode supported by Andor DLL """ AUTO = 0 OPEN = 1 CLOSE = 2 -OK_CODE = 20002 - +OK_CODE = 20002 # Status code associated with DRV_SUCCESS +# Error codes and strings defines by the DLL ERROR_DICT = { 20001: "DRV_ERROR_CODES", 20002: "DRV_SUCCESS", @@ -126,117 +123,65 @@ class ShutterMode(Enum): } -class Newton940(Base, CameraInterface): # Todo : rename class for any Andor camera - """ Hardware class for Andor Newton940 CCD spectroscopy cameras """ +class Main(Base, CameraInterface): + """ Hardware class for Andor CCD spectroscopy cameras + Tested with : + - Newton 940 + """ _dll_location = ConfigOption('dll_location', missing='error') _close_shutter_on_deactivate = ConfigOption('close_shutter_on_deactivate', False) + #todo: open shutter_on_activate ? - _default_cooler_status = ConfigOption('default_cooler_status', True) + _start_cooler_on_activate = ConfigOption('start_cooler_on_activate', True) _default_temperature = ConfigOption('default_temperature', 260) - _default_acquisition_mode = ConfigOption('default_acquisition_mode', 'SINGLE_SCAN') #todo: remove - _default_read_mode = ConfigOption('default_read_mode', 'IMAGE') #todo: remove - _default_readout_speed = ConfigOption('default_readout_speed', 50000) #todo: remove - _default_preamp_gain = ConfigOption('default_preamp_gain', 1) #todo: remove _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') - _default_exposure = ConfigOption('default_exposure', 1.0) #todo: remove - _default_shutter_status = ConfigOption('default_shutter_status', 'CLOSE') #todo: remove, but maybe close on deactivate ? - _default_active_tracks = ConfigOption('default_active_tracks', [246, 266]) #todo: remove - _default_binning = ConfigOption('default_binning', [1, 1]) #todo: remove - _default_ROI = ConfigOption('default_ROI', [1, 2048, 1, 512]) #todo: remove - _default_max_exposure_time = ConfigOption('default_max_exposure_time', 600) #todo: does this come from the dll and why forbid it ? - - _camera_name = 'Newton940'#todo: from config option or read from dll ? - - _cooler_status = _default_cooler_status - _temperature = _default_temperature - _max_cooling = -85 # todo - _acquisition_mode = _default_acquisition_mode - _read_mode = _default_read_mode - _readout_speed = _default_readout_speed - _preamp_gain = _default_preamp_gain - _trigger_mode = _default_trigger_mode - - _exposure = _default_exposure - _max_exposure_time = _default_max_exposure_time - _shutter_status = _default_shutter_status - _shutter_TTL = 1 - _shutter_closing_time = 100 # ms! - _shutter_opening_time = 100 # ms! - - _gain = 0 - _width = 0 - _height = 0 - _supported_read_mode = ReadMode - _live = False - - _scans = 1 - _acquiring = False - - _number_of_tracks = 1 - _binning = _default_binning - _ROI = _default_ROI - - _hbin = 1 - _vbin = 1 - _hstart = 1 - _hend = 2 - _vstart = 1 - _vend = 2 - - _constraints = {} # not todo: this is nice ! + _max_exposure_time = ConfigOption('max_exposure_time', 600) # todo: does this come from the dll and why forbid it ? + _min_temperature = 189 #todo: why ? _max_temperature = 262 # todo: why ? + # Declarations of attributes to make Pycharm happy + def __init__(self): + self._constraints = None + self._dll = None + self._active_tracks = None + self._image_advanced_parameters = None + ############################################################################## # Basic module activation/deactivation ############################################################################## - # is working - # secured OK - tested PV - SI OK - def on_activate(self): - """ Initialization performed during activation of the module. - """ + """ Initialization performed during activation of the module. """ try: - self.dll = ct.cdll.LoadLibrary(self._dll_location) + self._dll = ct.cdll.LoadLibrary(self._dll_location) except OSError: self.log.error('Error during dll loading of the Andor camera, check the dll path.') status_code = self.dll.Initialize() if status_code != OK_CODE: - self.log.error('Problem during camera (Andor/Newton) initialization') + self.log.error('Problem during camera initialization') return - self._constraints = self.get_constraints() - self._height, self._width = self.get_image_size() - - self.set_cooler_status(self._cooler_status) - self.set_temperature(self._temperature) + self._constraints = self._build_constraints() - self.set_acquisition_mode(self._acquisition_mode) #todo: done by logic - self.set_read_mode(self._read_mode) #todo: done by logic - self.set_readout_speed(self._readout_speed) #todo: done by logic - self.set_gain(self._preamp_gain) #todo: done by logic - self.set_trigger_mode(self._trigger_mode) + if self._constraints.has_cooler and self._start_cooler_on_activate: + self.set_cooler_on(True) - self.set_exposure_time(self._exposure) #todo: done by logic + self.set_read_mode(ReadMode.FVB) + self.set_trigger_mode(self._default_trigger_mode) + self.set_temperature_setpoint(self._default_temperature) - self.set_shutter_status(self._shutter_status) - - self._active_tracks = np.array(self._default_active_tracks) - self._hbin = self._binning[0] - self._vbin = self._binning[1] - self._hstart = self._ROI[0] - self._hend = self._ROI[1] - self._vstart = self._ROI[2] - self._vend = self._ROI[3] + self.set_acquisition_mode(AcquisitionMode.SINGLE_SCAN) + self._active_tracks = [] + self._image_advanced_parameters = None def on_deactivate(self): """ De-initialisation performed during deactivation of the module. """ - if not (self.get_ready_state()): + if self.module_state() == 'locked': self.stop_acquisition() if self._close_shutter_on_deactivate: - self.set_shutter_status('CLOSE') #todo: closed ? + self.set_shutter_open_state(False) try: self.dll.ShutDown() except: @@ -245,101 +190,94 @@ def on_deactivate(self): ############################################################################## # Error management ############################################################################## - def check(self, func_val): + def _check(self, func_val): """ Check routine for the received error codes. - :return: the dll function error code - Tested : no - """ + @param (int) func_val: Status code returned by the DLL + + @return: The DLL function error code + """ if not func_val == OK_CODE: - self.log.error('Error in Newton with error_code {0}:' - '{1}'.format(func_val, ERROR_DICT[func_val])) + self.log.error('Error in Andor camera with error_code {}:{}'.format(func_val, ERROR_DICT[func_val])) return func_val ############################################################################## - # Basic functions + # Constraints functions ############################################################################## + def _build_constraints(self): + """ Internal method that build the constraints once at initialisation + + This makes multiple call to the DLL, so it will be called only onced by on_activate + """ + constraints = Constraints() + constraints.name = self._get_name() + constraints.width, constraints.width = self._get_image_size() + constraints.pixel_size_width, constraints.pixel_size_width = self._get_pixel_size() + constraints.internal_gains = [1, 2, 4] # # todo : from hardware + constraints.readout_speeds = [50000, 1000000, 3000000] # todo : read from hardware + constraints.has_cooler = True # todo : from hardware ? + constraints.trigger_modes = list(TriggerMode.__members__) # todo : from hardware if only some are available ? + constraints.has_shutter = True # todo : from hardware ? + constraints.read_modes = [ReadMode.FVB] + if constraints.height > 1: + constraints.read_modes.extend([ReadMode.MULTIPLE_TRACKS, ReadMode.IMAGE, ReadMode.IMAGE_ADVANCED]) + return constraints + def get_constraints(self): """ Returns all the fixed parameters of the hardware which can be used by the logic. - @return: (dict) constraint dict : { - - 'name' : (str) give the camera manufacture name (ex : 'Newton940') - - 'image_size' : (tuple) ((int) image_width, (int) image_length) give the camera image size in pixels units, - - 'pixel_size' : (tuple) ((float) pixel_width, (float) pixel_length) give the pixels size in m, - - 'read_modes' : (list) [(str) read_mode, ..] give the available read modes of the camera (ex : ['FVB']), - - 'readout_speed' : (list) - - 'internal_gains' : (list) [(float) gain, ..] give the available internal gain which can be set - to the camera preamplifier, - - 'trigger_modes' : (list) [(str) trigger_mode, ..] give the available trigger modes of the camera, - - 'has_cooler' : (bool) give if the camera has temperature controller installed, - - (optional) : let this key empty if no shutter is installed ! - 'shutter_modes' : (ndarray) [(str) shutter_mode, ..] give the shutter modes available if any - shutter is installed. - + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware """ - #todo: there is many thing, a class would probably be preferable - - internal_gains = [1, 2, 4] # todo : read from hardware - readout_speeds = [50000, 1000000, 3000000] # todo : read from hardware - - constraints = { - 'name': self.get_name(), - 'image_size': self.get_image_size(), - 'pixel_size': self.get_pixel_size(), - 'read_modes': ['FVB', 'RANDOM_TRACK', 'IMAGE'], - 'readout_speeds': readout_speeds, - 'trigger_modes': ['INTERNAL', 'EXTERNAL', 'EXTERNAL_START', 'EXTERNAL_EXPOSURE', - 'SOFTWARE_TRIGGER', 'EXTERNAL_CHARGE_SHIFTING'], - 'acquisition_modes': ['SINGLE_SCAN'], - 'internal_gains': internal_gains, - 'has_cooler': True, - 'shutter_modes': ['AUTO', 'OPEN', 'CLOSE'], - } - return constraints + return self._constraints + ############################################################################## + # Basic functions + ############################################################################## def start_acquisition(self): """ Starts the acquisition """ self.check(self.dll.StartAcquisition()) - self.dll.WaitForAcquisition() # todo: this is not synchronous - def stop_acquisition(self): + def _wait_for_acquisition(self): + """ Internal function, can be used to wait till acquisition is finished """ + self.dll.WaitForAcquisition() + + def abort_acquisition(self): """ Aborts the acquisition """ self.check(self.dll.AbortAcquisition()) def get_acquired_data(self): - """ Return the last acquired data. + """ Return an array of last acquired data. - @return numpy array: image data in format [[row],[row]...] - """ - if self._read_mode == 'FVB': - height = 1 - if self._read_mode == 'RANDOM_TRACK': - height = self._number_of_tracks - if self._read_mode == 'IMAGE': - height = self._height - dim = int(self._width * height) - image_array = np.zeros(dim) - c_image_array = c_int * dim - c_image = c_image_array() + @return: Data in the format depending on the read mode. - status_code = self.dll.GetAcquiredData(pointer(c_image), dim) + Depending on the read mode, the format is : + 'FVB' : 1d array + 'MULTIPLE_TRACKS' : list of 1d arrays + 'IMAGE' 2d array of shape (width, height) + 'IMAGE_ADVANCED' 2d array of shape (width, height) + Each value might be a float or an integer. + """ + if self.get_read_mode() == ReadMode.FVB: + height = 1 + elif self.get_read_mode() == ReadMode.MULTIPLE_TRACKS: + height = len(self.get_active_tracks()) + elif self.get_read_mode() == ReadMode.IMAGE: + height = self.get_constraints().height + elif self.get_read_mode() == ReadMode.IMAGE_ADVANCED: + pass #todo + + dimension = int(self.get_constraints().width * height) + c_image_array = ct.c_int * dimension + c_image = c_image_array() + status_code = self.dll.GetAcquiredData(ct.pointer(c_image), dimension) if status_code != OK_CODE: - self.log.warning('Could not retrieve an image. {0}'.format(ERROR_DICT[status_code])) - else: - for i in range(len(c_image)): #todo: there must be something better here - image_array[i] = c_image[i] + self.log.error('Could not retrieve data from camera. {0}'.format(ERROR_DICT[status_code])) - return np.reshape(image_array, (self._width, height)) + if self.get_read_mode() == ReadMode.FVB: + return np.array(c_image) + else: + return np.reshape(np.array(c_image), (self._width, height)).transpose() ############################################################################## # Read mode functions @@ -347,30 +285,33 @@ def get_acquired_data(self): def get_read_mode(self): """ Getter method returning the current read mode used by the camera. - @return (str): read mode + @return (ReadMode): Current read mode """ return self._read_mode - def set_read_mode(self, read_mode): + def set_read_mode(self, value): """ Setter method setting the read mode used by the camera. - @param (str) read_mode: read mode among those defined in the self.get_constraint - """ + @param (ReadMode) value: read mode to set + """ - if hasattr(ReadMode, read_mode) and (read_mode in self._constraints['read_modes']): - n_mode = c_int(getattr(ReadMode, read_mode).value) - self.check(self.dll.SetReadMode(n_mode)) - else: + if value not in self.get_constraints().read_modes: self.log.error('read_mode not supported') return - self._read_mode = read_mode + conversion_dict = {ReadMode.FVB: ReadModeDLL.FVB, + ReadMode.MULTIPLE_TRACKS: ReadModeDLL.RANDOM_TRACK, + ReadMode.IMAGE: ReadModeDLL.IMAGE, + ReadMode.IMAGE_ADVANCED: ReadModeDLL.IMAGE} - if read_mode == 'IMAGE': - self.set_active_image(1, 1, 1, self._height, 1, self._width) + n_mode = conversion_dict[value].value + self.check(self.dll.SetReadMode(n_mode)) + self._read_mode = value - elif read_mode == 'RANDOM_TRACK': - self.set_active_tracks(self._active_tracks) + if value == ReadMode.IMAGE or value == ReadMode.IMAGE_ADVANCED: + self._update_image() + elif value == ReadMode.MULTIPLE_TRACKS(): + self._update_active_tracks() def get_readout_speed(self): """ Get the current readout speed (in Hz) @@ -379,17 +320,17 @@ def get_readout_speed(self): """ return self._readout_speed - def set_readout_speed(self, readout_speed): + def set_readout_speed(self, value): """ Set the readout speed (in Hz) - @param (float) readout_speed: horizontal shift in Hz + @param (float) value: horizontal readout speed in Hz """ - if readout_speed in self._constraints['readout_speeds']: - readout_speed_index = self._constraints['readout_speeds'].index(readout_speed) + if value in self._constraints['readout_speeds']: + readout_speed_index = self._constraints['readout_speeds'].index(value) self.check(self.dll.SetHSSpeed(0, readout_speed_index)) - self._readout_speed = readout_speed + self._readout_speed = value else: - self.log.error('Readout_speed value error, value {} is not in correct.'.format(readout_speed)) + self.log.error('Readout_speed value error, value {} is not in correct.'.format(value)) def get_active_tracks(self): """ Getter method returning the read mode tracks parameters of the camera. @@ -423,7 +364,7 @@ def get_active_image(self): active_image_parameters = [self._vbin, self._hbin, self._vstart, self._vend, self._hstart, self._hend] return active_image_parameters - def set_active_image(self, vbin, hbin, vstart, vend, hstart, hend): + def _set_image(self, vbin, hbin, vstart, vend, hstart, hend): """ Setter method setting the read mode image parameters of the camera. @param hbin: (int) horizontal pixel binning @@ -494,21 +435,21 @@ def _get_acquisition_timings(self): @return (dict): dict containing keys 'exposure', 'accumulate', 'kinetic' and their values in seconds """ exposure, accumulate, kinetic = c_float(), c_float(), c_float() - self.check(self.dll.GetAcquisitionTimings(byref(exposure), byref(accumulate), byref(kinetic))) + self.check(self.dll.GetAcquisitionTimings(ct.byref(exposure), ct.byref(accumulate), ct.byref(kinetic))) return {'exposure': exposure.value, 'accumulate': accumulate.value, 'kinetic': kinetic.value} - def set_exposure_time(self, exposure_time): + def set_exposure_time(self, value): """ Set the exposure time in seconds - @param (float) exposure_time: desired new exposure time + @param (float) value: desired new exposure time """ - if exposure_time < 0: + if value < 0: self.log.error('Exposure_time can not be negative.') return - if exposure_time > self._max_exposure_time: + if value > self._max_exposure_time: self.log.error('Exposure time is above the high limit : {0} s'.format(self._max_exposure_time)) return - self.check(self.dll.SetExposureTime(c_float(exposure_time))) + self.check(self.dll.SetExposureTime(c_float(value))) def get_gain(self): """ Get the gain @@ -517,15 +458,15 @@ def get_gain(self): """ return self._preamp_gain #todo: read from hardware ? - def set_gain(self, gain): + def set_gain(self, value): """ Set the gain - @param (float) gain: desired new gain + @param (float) value: desired new gain """ - if gain not in self._constraints['internal_gains']: - self.log.error('gain value {} is not available.'.format(gain)) + if value not in self._constraints['internal_gains']: + self.log.error('gain value {} is not available.'.format(value)) return - gain_index = self._constraints['internal_gains'].index(gain) + gain_index = self._constraints['internal_gains'].index(value) self.check(self.dll.SetPreAmpGain(gain_index)) ############################################################################## @@ -538,33 +479,33 @@ def get_trigger_mode(self): """ return self._trigger_mode #todo: read from hardware ? - def set_trigger_mode(self, trigger_mode): + def set_trigger_mode(self, value): """ Setter method for the trigger mode used by the camera. - @param (str) trigger_mode: trigger mode (must be compared to a dict) + @param (str) value: trigger mode (must be compared to a dict) """ - if hasattr(TriggerMode, trigger_mode) \ - and (trigger_mode in self._constraints['trigger_modes']): - n_mode = c_int(getattr(TriggerMode, trigger_mode).value) + if hasattr(TriggerMode, value) \ + and (value in self._constraints['trigger_modes']): + n_mode = c_int(getattr(TriggerMode, value).value) self.check(self.dll.SetTriggerMode(n_mode)) - self._trigger_mode = trigger_mode + self._trigger_mode = value else: - self.log.warning('Trigger mode {} is not supported.'.format(trigger_mode)) + self.log.warning('Trigger mode {} is not supported.'.format(value)) return - self._trigger_mode = trigger_mode + self._trigger_mode = value return ############################################################################## # Shutter mode functions ############################################################################## - def get_shutter_status(self): + def get_shutter_open_state(self): """ Getter method returning if the shutter is open. @return (bool): @bool shutter open ? #todo: status tested : yes SI check : yes """ - return self._shutter_status #todo from hardware ? + return self._shutter_status #todo from hardware def set_shutter_status(self, shutter_status): """ Setter method for the shutter state. @@ -613,7 +554,7 @@ def get_temperature(self): @return (float): temperature (in Kelvin) """ temp = c_int32() - self.dll.GetTemperature(byref(temp)) + self.dll.GetTemperature(ct.byref(temp)) return temp.value + 273.15 def set_temperature(self, temperature): @@ -640,7 +581,7 @@ def get_name(self): @return (str): local camera name with serial number """ serial = ct.c_int() - self.check(self.dll.GetCameraSerialNumber(byref(serial))) + self.check(self.dll.GetCameraSerialNumber(ct.byref(serial))) name = self._camera_name + " serial number " + str(serial.value) return name @@ -651,8 +592,8 @@ def get_image_size(self): """ nx_px = ct.c_int() ny_px = ct.c_int() - self.check(self.dll.GetDetector(byref(nx_px), byref(ny_px))) - return ny_px.value, nx_px.value + self.check(self.dll.GetDetector(ct.byref(nx_px), ct.byref(ny_px))) + return nx_px.value, ny_px.value def get_pixel_size(self): """ Get the physical pixel size (width, height) in meter @@ -661,7 +602,7 @@ def get_pixel_size(self): """ x_px = ct.c_float() y_px = ct.c_float() - self.check(self.dll.GetPixelSize(byref(x_px), byref(y_px))) + self.check(self.dll.GetPixelSize(ct.byref(x_px), ct.byref(y_px))) return y_px.value * 1e-6, x_px.value * 1e-6 def get_ready_state(self): @@ -670,7 +611,7 @@ def get_ready_state(self): @return (bool): True if camera state is idle """ code = ct.c_int() - self.check(self.dll.GetStatus(byref(code))) + self.check(self.dll.GetStatus(ct.byref(code))) return code.value == OK_CODE def _get_current_config(self): From 62df9714940b220cba8a2231df8f2d890c7f2a61 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 30 Apr 2020 19:14:47 +0200 Subject: [PATCH 29/49] small update interface --- interface/camera_complete_interface.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index f20d71806d..068c938723 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -44,8 +44,10 @@ class Constraints: """ Class defining formally the hardware constraints """ def __init__(self): self.name = '', # Camera manufacture name (ex : 'Newton940') - self.image_size = (None, None), # Camera size in pixels (width, height) - self.pixel_size = (None, None), # Physical size of the pixels in meter (width, height) + self.width = None, # Camera width in pixels + self.height = None, # Camera height in pixels + self.pixel_size_width = None, # Physical width of the pixels in meter + self.pixel_size_height = None # Physical height of the pixels in meter self.read_modes = [], # Read mode supported by the camera (see ReadMode class) self.internal_gains = [], # Internal gains supported by the camera (list of float) self.readout_speeds = [], # Readout speed supported by the camera, in Hz (list of float) @@ -85,7 +87,7 @@ def start_acquisition(self): pass @abstract_interface_method - def stop_acquisition(self): + def abort_acquisition(self): """ Abort acquisition """ pass From 69ceaacff59e0e813f14d36ac230bd4d76300787 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 4 May 2020 15:47:00 +0200 Subject: [PATCH 30/49] added get_ready_state to the camera interface --- interface/camera_complete_interface.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/camera_complete_interface.py b/interface/camera_complete_interface.py index 068c938723..e5552b8840 100644 --- a/interface/camera_complete_interface.py +++ b/interface/camera_complete_interface.py @@ -91,6 +91,16 @@ def abort_acquisition(self): """ Abort acquisition """ pass + @abstract_interface_method + def get_ready_state(self): + """ Get the status of the camera, to know if the acquisition is finished or still ongoing. + + @return (bool): True if the camera is ready, False if an acquisition is ongoing + + As there is no synchronous acquisition in the interface, the logic needs a way to check the acquisition state. + """ + pass + @abstract_interface_method def get_acquired_data(self): """ Return an array of last acquired data. From 38467f9608499c7c16a65584a939125950c68ddb Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 4 May 2020 15:58:23 +0200 Subject: [PATCH 31/49] finished working on Andor camera hardware --- hardware/camera/andor/Newton_940.py | 372 +++++++++++++++------------- 1 file changed, 197 insertions(+), 175 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 62715e8b5e..7c2b9869ca 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -131,15 +131,17 @@ class Main(Base, CameraInterface): """ _dll_location = ConfigOption('dll_location', missing='error') _close_shutter_on_deactivate = ConfigOption('close_shutter_on_deactivate', False) - #todo: open shutter_on_activate ? + # todo: open shutter_on_activate ? _start_cooler_on_activate = ConfigOption('start_cooler_on_activate', True) _default_temperature = ConfigOption('default_temperature', 260) _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') _max_exposure_time = ConfigOption('max_exposure_time', 600) # todo: does this come from the dll and why forbid it ? + _shutter_TTL = ConfigOption('shutter_TTL', 1) # todo: explain what this is for the user + _shutter_switching_time = ConfigOption('shutter_switching_time', 100e-3) # todo: explain what this is for the user - _min_temperature = 189 #todo: why ? - _max_temperature = 262 # todo: why ? + _min_temperature = -85 # todo: why ? In this module internally, we can work with degree celsius, as andor users will be used to this. Still, this look rather arbitrary + _max_temperature = -10 # todo: why ? same # Declarations of attributes to make Pycharm happy def __init__(self): @@ -147,6 +149,12 @@ def __init__(self): self._dll = None self._active_tracks = None self._image_advanced_parameters = None + self._readout_speed = None + self._read_mode = None + self._trigger_mode = None + self._shutter_status = None + self._cooler_status = None + self._temperature_setpoint = None ############################################################################## # Basic module activation/deactivation @@ -157,8 +165,8 @@ def on_activate(self): self._dll = ct.cdll.LoadLibrary(self._dll_location) except OSError: self.log.error('Error during dll loading of the Andor camera, check the dll path.') - - status_code = self.dll.Initialize() + # todo: camera selection by SN ? + status_code = self._dll.Initialize() if status_code != OK_CODE: self.log.error('Problem during camera initialization') return @@ -168,11 +176,11 @@ def on_activate(self): if self._constraints.has_cooler and self._start_cooler_on_activate: self.set_cooler_on(True) - self.set_read_mode(ReadMode.FVB) + self.set_read_mode(ReadMode.FVB) # todo: what if not ? self.set_trigger_mode(self._default_trigger_mode) self.set_temperature_setpoint(self._default_temperature) - self.set_acquisition_mode(AcquisitionMode.SINGLE_SCAN) + self._set_acquisition_mode(AcquisitionMode.SINGLE_SCAN) self._active_tracks = [] self._image_advanced_parameters = None @@ -183,7 +191,7 @@ def on_deactivate(self): if self._close_shutter_on_deactivate: self.set_shutter_open_state(False) try: - self.dll.ShutDown() + self._dll.ShutDown() except: self.log.warning('Error while shutting down Andor camera via dll.') @@ -215,8 +223,8 @@ def _build_constraints(self): constraints.pixel_size_width, constraints.pixel_size_width = self._get_pixel_size() constraints.internal_gains = [1, 2, 4] # # todo : from hardware constraints.readout_speeds = [50000, 1000000, 3000000] # todo : read from hardware - constraints.has_cooler = True # todo : from hardware ? - constraints.trigger_modes = list(TriggerMode.__members__) # todo : from hardware if only some are available ? + constraints.has_cooler = True # todo : from hardware ? + constraints.trigger_modes = list(TriggerMode.__members__) # todo : from hardware if only some are available ? constraints.has_shutter = True # todo : from hardware ? constraints.read_modes = [ReadMode.FVB] if constraints.height > 1: @@ -235,17 +243,33 @@ def get_constraints(self): ############################################################################## def start_acquisition(self): """ Starts the acquisition """ - self.check(self.dll.StartAcquisition()) + self._check(self._dll.StartAcquisition()) def _wait_for_acquisition(self): """ Internal function, can be used to wait till acquisition is finished """ - self.dll.WaitForAcquisition() + self._dll.WaitForAcquisition() def abort_acquisition(self): """ Aborts the acquisition """ - self.check(self.dll.AbortAcquisition()) + self._check(self._dll.AbortAcquisition()) + + def get_ready_state(self): # todo: check this function, i've guessed the dll behavior... + """ Get the status of the camera, to know if the acquisition is finished or still ongoing. + + @return (bool): True if the camera is ready, False if an acquisition is ongoing + + As there is no synchronous acquisition in the interface, the logic needs a way to check the acquisition state. + """ + code = ct.c_int() + self._dll.GetStatus(ct.byref(code)) + if ERROR_DICT[code.value] == 'DRV_IDLE': + return True + elif ERROR_DICT[code.value] == 'DRV_ACQUIRING': + return False + else: + self._check(code.value) - def get_acquired_data(self): + def get_acquired_data(self): # todo: test for every mode """ Return an array of last acquired data. @return: Data in the format depending on the read mode. @@ -258,6 +282,7 @@ def get_acquired_data(self): Each value might be a float or an integer. """ + width = self.get_constraints().width if self.get_read_mode() == ReadMode.FVB: height = 1 elif self.get_read_mode() == ReadMode.MULTIPLE_TRACKS: @@ -265,19 +290,21 @@ def get_acquired_data(self): elif self.get_read_mode() == ReadMode.IMAGE: height = self.get_constraints().height elif self.get_read_mode() == ReadMode.IMAGE_ADVANCED: - pass #todo + params = self.get_image_advanced_parameters() + height = (params.vertical_end - params.vertical_start)/params.vertical_binning + width = (params.horizontal_end - params.horizontal_start)/params.horizontal_binning - dimension = int(self.get_constraints().width * height) + dimension = int(width * height) c_image_array = ct.c_int * dimension c_image = c_image_array() - status_code = self.dll.GetAcquiredData(ct.pointer(c_image), dimension) + status_code = self._dll.GetAcquiredData(ct.pointer(c_image), dimension) if status_code != OK_CODE: self.log.error('Could not retrieve data from camera. {0}'.format(ERROR_DICT[status_code])) if self.get_read_mode() == ReadMode.FVB: return np.array(c_image) else: - return np.reshape(np.array(c_image), (self._width, height)).transpose() + return np.reshape(np.array(c_image), (width, height)).transpose() ############################################################################## # Read mode functions @@ -305,12 +332,13 @@ def set_read_mode(self, value): ReadMode.IMAGE_ADVANCED: ReadModeDLL.IMAGE} n_mode = conversion_dict[value].value - self.check(self.dll.SetReadMode(n_mode)) - self._read_mode = value + status_code = self._check(self._dll.SetReadMode(n_mode)) + if status_code == OK_CODE: + self._read_mode = value if value == ReadMode.IMAGE or value == ReadMode.IMAGE_ADVANCED: self._update_image() - elif value == ReadMode.MULTIPLE_TRACKS(): + elif value == ReadMode.MULTIPLE_TRACKS: self._update_active_tracks() def get_readout_speed(self): @@ -318,16 +346,16 @@ def get_readout_speed(self): @return (float): the readout_speed (Horizontal shift) in Hz """ - return self._readout_speed + return self._readout_speed # todo: not in dll ? def set_readout_speed(self, value): """ Set the readout speed (in Hz) @param (float) value: horizontal readout speed in Hz """ - if value in self._constraints['readout_speeds']: - readout_speed_index = self._constraints['readout_speeds'].index(value) - self.check(self.dll.SetHSSpeed(0, readout_speed_index)) + if value in self.get_constraints().readout_speeds: + readout_speed_index = self.get_constraints().readout_speeds.index(value) + self._check(self._dll.SetHSSpeed(0, readout_speed_index)) self._readout_speed = value else: self.log.error('Readout_speed value error, value {} is not in correct.'.format(value)) @@ -336,106 +364,103 @@ def get_active_tracks(self): """ Getter method returning the read mode tracks parameters of the camera. @return (list): active tracks positions [(start_1, end_1), (start_2, end_2), ... ] + + This getter is not available in the dll, so its state is handled by this module # todo: confirm ? """ return self._active_tracks - def set_active_tracks(self, active_tracks): + def set_active_tracks(self, value): """ Setter method for the active tracks of the camera. - @param (list) active_tracks: active tracks positions as [(start_1, end_1), (start_2, end_2), ... ] + @param (list) value: active tracks positions as [(start_1, end_1), (start_2, end_2), ... ] """ - if self._read_mode != 'RANDOM_TRACK': - self.log.error('Active tracks are defined outside of RANDOM_TRACK mode.') - return - number_of_tracks = int(len(active_tracks)) - active_tracks = [item for item_tuple in active_tracks for item in item_tuple] #todo: decompose this, do not use imbricated loops in one line loop - self.dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] - self.check(self.dll.SetRandomTracks(number_of_tracks, active_tracks.ctypes.data)) - self._active_tracks = active_tracks - self._number_of_tracks = number_of_tracks - - def get_active_image(self): - """ Getter method returning the read mode image parameters of the camera. - - @return: (np array) active image parameters [hbin, vbin, hstart, hend, vstart, vend] - tested : yes - SI check : yes + if self.get_read_mode() != ReadMode.MULTIPLE_TRACKS: + self.log.warning('Active tracks are defined outside of MULTIPLE_TRACKS mode.') + + self._active_tracks = value + self._update_active_tracks() + + def _update_active_tracks(self): + """ Internal function that send the current active tracks to the DLL """ + flatten_tracks = np.array(self._active_tracks).flatten() + self._dll.SetRandomTracks.argtypes = [ct.c_int32, ct.c_void_p] + status_code = self._check(self._dll.SetRandomTracks(len(self._active_tracks), flatten_tracks.ctypes.data)) + self._check(status_code) + if status_code != OK_CODE: # Clear tracks if an error has occurred + self._active_tracks = [] + + def get_image_advanced_parameters(self): + """ Getter method returning the image parameters of the camera. + + @return (ImageAdvancedParameters): Current image advanced parameters + + Should only be used while in IMAGE_ADVANCED mode """ - active_image_parameters = [self._vbin, self._hbin, self._vstart, self._vend, self._hstart, self._hend] - return active_image_parameters + return self._advanced_image_parameters - def _set_image(self, vbin, hbin, vstart, vend, hstart, hend): + def set_image_advanced_parameters(self, value): """ Setter method setting the read mode image parameters of the camera. - @param hbin: (int) horizontal pixel binning - @param vbin: (int) vertical pixel binning - @param hstart: (int) image starting row - @param hend: (int) image ending row - @param vstart: (int) image starting column - @param vend: (int) image ending column - @return: nothing - tested : yes - SI check : yes + @param (ImageAdvancedParameters) value: Parameters to set + + Should only be used while in IMAGE_ADVANCED mode """ - hbin, vbin, hstart, hend, vstart, vend = c_int(hbin), c_int(vbin), c_int(hstart), c_int(hend),\ - c_int(vstart), c_int(vend) + self._image_advanced_parameters = value + self._update_image() - status_code = self.check(self.dll.SetImage(hbin, vbin, hstart, hend, vstart, vend)) - if status_code == OK_CODE: - self._hbin = hbin.value - self._vbin = vbin.value - self._hstart = hstart.value - self._hend = hend.value - self._vstart = vstart.value - self._vend = vend.value - self._width = int((self._hend - self._hstart + 1) / self._hbin) - self._height = int((self._vend - self._vstart + 1) / self._vbin) - self._ROI = (self._hstart, self._hend, self._vstart, self._vend) - self._binning = (self._hbin, self._vbin) - else: - self.log.error('Call to set_active_image went wrong:{0}'.format(ERROR_DICT[status_code])) - return + def _update_image(self): + """ Internal method that send the current appropriate image settings to the DLL""" + + if self.get_read_mode() == ReadMode.IMAGE: + status_code = self._dll.SetImage(1, 1, 0, self.get_constraints().width, 0, self.get_constraints().height) + self._check(status_code) + + elif self.get_read_mode() == ReadMode.IMAGE_ADVANCED: + params = self._image_advanced_parameters + status_code = self._dll.SetImage(int(params.horizontal_binning), int(params.vertical_binning), + int(params.horizontal_start), int(params.horizontal_end), + int(params.vertical_start), int(params.vertical_end)) + self._check(status_code) ############################################################################## # Acquisition mode functions ############################################################################## - - def get_acquisition_mode(self): + def _get_acquisition_mode(self): """ Getter method returning the current acquisition mode used by the camera. @return (str): acquisition mode """ return self._acquisition_mode - def set_acquisition_mode(self, acquisition_mode): + def _set_acquisition_mode(self, value): """ Setter method setting the acquisition mode used by the camera. - @param acquisition_mode: @str read mode (must be compared to a dict) - """ + @param (str|AcquisitionMode): Acquisition mode as a string or an object - if hasattr(AcquisitionMode, acquisition_mode) \ - and (acquisition_mode in self._constraints['acquisition_modes']): - n_mode = c_int(getattr(AcquisitionMode, acquisition_mode).value) - self.check(self.dll.SetAcquisitionMode(n_mode)) - else: - self.log.error('{} mode is not supported'.format(acquisition_mode)) + This method is not part of the interface, so we might need to use it from a script directly. Hence, here + it is worth it to accept a string. + """ + if isinstance(value, str) and value in AcquisitionMode.__members__: + value = AcquisitionMode[value] + if not isinstance(value, AcquisitionMode): + self.log.error('{} acquisition mode is not supported'.format(value)) return - self._acquisition_mode = acquisition_mode - return + n_mode = ct.c_int(value.value) + self._check(self._dll.SetAcquisitionMode(n_mode)) def get_exposure_time(self): """ Get the exposure time in seconds @return (float) : exposure time in s """ - return self._get_acquisition_timings['exposure'] + return self._get_acquisition_timings()['exposure'] def _get_acquisition_timings(self): """ Get the acquisitions timings from the dll @return (dict): dict containing keys 'exposure', 'accumulate', 'kinetic' and their values in seconds """ - exposure, accumulate, kinetic = c_float(), c_float(), c_float() - self.check(self.dll.GetAcquisitionTimings(ct.byref(exposure), ct.byref(accumulate), ct.byref(kinetic))) + exposure, accumulate, kinetic = ct.c_float(), ct.c_float(), ct.c_float() + self._check(self._dll.GetAcquisitionTimings(ct.byref(exposure), ct.byref(accumulate), ct.byref(kinetic))) return {'exposure': exposure.value, 'accumulate': accumulate.value, 'kinetic': kinetic.value} def set_exposure_time(self, value): @@ -444,30 +469,30 @@ def set_exposure_time(self, value): @param (float) value: desired new exposure time """ if value < 0: - self.log.error('Exposure_time can not be negative.') + self.log.error('Exposure_time ({} s) can not be negative.'.format(value)) return if value > self._max_exposure_time: - self.log.error('Exposure time is above the high limit : {0} s'.format(self._max_exposure_time)) + self.log.error('Exposure time ({} s) is above the high limit ({} s)'.format(value, self._max_exposure_time)) return - self.check(self.dll.SetExposureTime(c_float(value))) + self._check(self._dll.SetExposureTime(ct.c_float(value))) def get_gain(self): """ Get the gain @return (float): exposure gain """ - return self._preamp_gain #todo: read from hardware ? + return self._preamp_gain # todo: read from hardware ? def set_gain(self, value): """ Set the gain - @param (float) value: desired new gain + @param (float) value: New gain, value should be one in the constraints internal_gains list. """ - if value not in self._constraints['internal_gains']: + if value not in self.get_constraints().internal_gains: self.log.error('gain value {} is not available.'.format(value)) return - gain_index = self._constraints['internal_gains'].index(value) - self.check(self.dll.SetPreAmpGain(gain_index)) + gain_index = self.get_constraints().internal_gains.index(value) + self._check(self._dll.SetPreAmpGain(gain_index)) ############################################################################## # Trigger mode functions @@ -477,152 +502,150 @@ def get_trigger_mode(self): @return (str): current trigger mode """ - return self._trigger_mode #todo: read from hardware ? + return self._trigger_mode # todo: read from hardware ? def set_trigger_mode(self, value): """ Setter method for the trigger mode used by the camera. @param (str) value: trigger mode (must be compared to a dict) """ - if hasattr(TriggerMode, value) \ - and (value in self._constraints['trigger_modes']): - n_mode = c_int(getattr(TriggerMode, value).value) - self.check(self.dll.SetTriggerMode(n_mode)) - self._trigger_mode = value - else: - self.log.warning('Trigger mode {} is not supported.'.format(value)) + if value not in self.get_constraints().trigger_modes: + self.log.error('Trigger mode {} is not declared by hardware.'.format(value)) return - self._trigger_mode = value - return + n_mode = TriggerMode[value].value + status_code = self._check(self._dll.SetTriggerMode(n_mode)) + if status_code == OK_CODE: + self._trigger_mode = value ############################################################################## # Shutter mode functions ############################################################################## def get_shutter_open_state(self): - """ Getter method returning if the shutter is open. + """ Getter method returning the shutter mode. - @return (bool): @bool shutter open ? #todo: status - tested : yes - SI check : yes + @return (bool): True if the shutter is open, False of closed """ - return self._shutter_status #todo from hardware + if not self.get_constraints().has_shutter: + self.log.error('Can not get state of the shutter, camera does not have a shutter') + return self._shutter_status # todo from hardware - def set_shutter_status(self, shutter_status): - """ Setter method for the shutter state. + def set_shutter_open_state(self, value): + """ Setter method setting the shutter mode. - @param (str): shutter_status + @param (bool) value: True to open, False tp close """ - - if hasattr(ShutterMode, shutter_status) \ - and (shutter_status in self._constraints['shutter_modes']): - mode = c_int(getattr(ShutterMode, shutter_status).value) - self.check(self.dll.SetShutter(self._shutter_TTL, mode, - self._shutter_closing_time, self._shutter_opening_time)) - self._shutter_status = shutter_status - else: - self.log.warning('HW/Newton940/set_shutter_status() : ' - '{0} mode is not supported'.format(shutter_status)) - return - self._shutter_status = shutter_status - return + if not self.get_constraints().has_shutter: + self.log.error('Can not set state of the shutter, camera does not have a shutter') + mode = ShutterMode.OPEN if value else ShutterMode.CLOSE + mode = ct.c_int(mode.value) # todo: needed for interger ? + shutter_TTL = int(self._shutter_TTL) + shutter_time = int(round(self._shutter_switching_time*1e3)) # DLL use ms + status_code = self._check(self._dll.SetShutter(shutter_TTL, mode, shutter_time, shutter_time)) + if status_code == OK_CODE: + self._shutter_status = value ############################################################################## # Temperature functions ############################################################################## - def get_cooler_status(self): - """ Getter method returning the cooler status if ON or OFF. + def get_cooler_on(self): + """ Getter method returning the cooler status - @return (bool): True if ON or False if OFF or 0 if error + @return (bool): True if the cooler is on """ - return self._cooler_status #todo: from harware + return self._cooler_status # todo: from harware - def set_cooler_status(self, cooler_status): - """ Setter method for the cooler status. + def set_cooler_on(self, value): + """ Setter method for the the cooler status - @param (bool) cooler_status: True if ON or False if OFF + @param (bool) value: True to turn it on, False to turn it off """ - if cooler_status: - self.check(self.dll.CoolerON()) - self._cooler_status = True #todo: handled by camera + if value: + status_code = self._dll.CoolerON() else: - self.check(self.dll.CoolerOFF()) - self._cooler_status = False #todo: handled by camera + status_code = self._dll.CoolerOFF() + self._check(status_code) + if status_code == OK_CODE: + self._cooler_status = value # todo: no need if handled by hardware def get_temperature(self): """ Getter method returning the temperature of the camera. @return (float): temperature (in Kelvin) + + The dll uses integers in celsius, so the result will always end with .15, too bad. """ - temp = c_int32() - self.dll.GetTemperature(ct.byref(temp)) + temp = ct.c_int32() + self._dll.GetTemperature(ct.byref(temp)) return temp.value + 273.15 - def set_temperature(self, temperature): - """ Setter method for the the temperature setpoint of the camera. + def get_temperature_setpoint(self): + """ Getter method for the temperature setpoint of the camera. - @param (float) temperature: temperature (in Kelvin) + @return (float): Current setpoint in Kelvin """ - temperature = int(temperature) #todo: conversion to integer might mess things up, this has do ne checked nicely - if self._min_temperature < temperature < self._max_temperature: - temperature = int(temperature-273.15) - self.check(self.dll.SetTemperature(temperature)) - self._temperature = temperature+273.15 - else: - self.log.warning('Temperature {} Kelvin is not in the validity range.') + return self._temperature_setpoint #todo: not in dll ? - #todo: setpoint getter ? + def set_temperature_setpoint(self, value): + """ Setter method for the the temperature setpoint of the camera. + + @param (float) value: New setpoint in Kelvin + """ + temperature = int(round(value + 273.15)) + if not(self._min_temperature < temperature < self._max_temperature): + self.log.error('Temperature {}°C is not in the validity range.') + return + status_code = self._check(self._dll.SetTemperature(temperature)) + if status_code == OK_CODE: + self._temperature_setpoint = temperature ############################################################################## # Internal functions, for constraints preparation ############################################################################## - def get_name(self): + def _get_serial_number(self): + """ Get the serial number of the camera as a string + + @return (str): serial number of the camera + """ + serial = ct.c_int() + self._check(self._dll.GetCameraSerialNumber(ct.byref(serial))) + return serial.value + + def _get_name(self): """ Get a name for the camera @return (str): local camera name with serial number """ - serial = ct.c_int() - self.check(self.dll.GetCameraSerialNumber(ct.byref(serial))) - name = self._camera_name + " serial number " + str(serial.value) - return name + return "Camera SN: {}".format(self._get_serial_number()) - def get_image_size(self): + def _get_image_size(self): """ Returns the sensor size in pixels (width, height) @return tuple(int, int): number of pixel in width and height """ nx_px = ct.c_int() ny_px = ct.c_int() - self.check(self.dll.GetDetector(ct.byref(nx_px), ct.byref(ny_px))) + self._check(self._dll.GetDetector(ct.byref(nx_px), ct.byref(ny_px))) return nx_px.value, ny_px.value - def get_pixel_size(self): + def _get_pixel_size(self): """ Get the physical pixel size (width, height) in meter @return tuple(float, float): physical pixel size in meter """ x_px = ct.c_float() y_px = ct.c_float() - self.check(self.dll.GetPixelSize(ct.byref(x_px), ct.byref(y_px))) + self._check(self._dll.GetPixelSize(ct.byref(x_px), ct.byref(y_px))) return y_px.value * 1e-6, x_px.value * 1e-6 - def get_ready_state(self): - """ Get the state of the camera to know if the acquisition is finished or not yet. - - @return (bool): True if camera state is idle - """ - code = ct.c_int() - self.check(self.dll.GetStatus(ct.byref(code))) - return code.value == OK_CODE - def _get_current_config(self): """ Internal helper method to get the camera parameters in a printable dict. @return (dict): dictionary with camera current configuration. """ - config = { #todo use getters for most of them - 'camera ID..................................': self.get_name(), - 'sensor size (pixels).......................': self.get_image_size(), - 'pixel size (m)............................': self.get_pixel_size(), + config = { #todo use getters for most of them + 'camera ID..................................': self._get_name(), + 'sensor size (pixels).......................': self._get_image_size(), + 'pixel size (m)............................': self._get_pixel_size(), 'acquisition mode...........................': self._acquisition_mode, 'read mode..................................': self._read_mode, 'readout speed (Hz).........................': self._readout_speed, @@ -631,7 +654,6 @@ def _get_current_config(self): 'exposure_time..............................': self._exposure, 'ROI geometry (readmode = IMAGE)............': self._ROI, 'ROI binning (readmode = IMAGE).............': self._binning, - 'number of tracks (readmode = RANDOM TRACK).': self._number_of_tracks, 'tracks definition (readmode = RANDOM TRACK)': self._active_tracks, 'temperature (K)............................': self._temperature, 'shutter_status.............................': self._shutter_status, From 29eca4d7a5e31c5d6fbe305b9437378ea93d10e1 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 4 May 2020 16:15:57 +0200 Subject: [PATCH 32/49] renamed camera interface --- hardware/camera/andor/Newton_940.py | 5 +++-- ...omplete_interface.py => spectroscopy_camera_interface.py} | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename interface/{camera_complete_interface.py => spectroscopy_camera_interface.py} (99%) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 7c2b9869ca..c78cd8a239 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -31,7 +31,8 @@ from core.module import Base from core.configoption import ConfigOption -from interface.camera_complete_interface import CameraInterface, ReadMode, Constraints, ImageAdvancedParameters +from interface.spectroscopy_camera_interface import SpectroscopyCameraInterface +from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters # Bellow are the classes used by Andor dll. They are not par of Qudi interfaces @@ -123,7 +124,7 @@ class ShutterMode(Enum): } -class Main(Base, CameraInterface): +class Main(Base, SpectroscopyCameraInterface): """ Hardware class for Andor CCD spectroscopy cameras Tested with : diff --git a/interface/camera_complete_interface.py b/interface/spectroscopy_camera_interface.py similarity index 99% rename from interface/camera_complete_interface.py rename to interface/spectroscopy_camera_interface.py index e5552b8840..a4d4a6a0b3 100644 --- a/interface/camera_complete_interface.py +++ b/interface/spectroscopy_camera_interface.py @@ -67,7 +67,7 @@ def __init__(self): self.vertical_end = None # Has to be an integer -class CameraInterface(metaclass=InterfaceMetaclass): +class SpectroscopyCameraInterface(metaclass=InterfaceMetaclass): """ This interface is used to manage a camera used for spectroscopy """ @abstract_interface_method From 94ebbc17f83f6fc956515cb98adbcc77bdef4b73 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 10:06:05 +0200 Subject: [PATCH 33/49] added a dummy for spectroscopy camera interface --- hardware/spectroscopy_camera_dummy.py | 376 ++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 hardware/spectroscopy_camera_dummy.py diff --git a/hardware/spectroscopy_camera_dummy.py b/hardware/spectroscopy_camera_dummy.py new file mode 100644 index 0000000000..aeafa5634b --- /dev/null +++ b/hardware/spectroscopy_camera_dummy.py @@ -0,0 +1,376 @@ +# -*- coding: utf-8 -*- +""" +This module contains fake a spectrometer camera. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +import numpy as np +from core.module import Base +from interface.spectroscopy_camera_interface import SpectroscopyCameraInterface +from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters + +from qtpy import QtCore + + +class Main(Base, SpectroscopyCameraInterface): + """ This module is the dummy module for the SpectroscopyCameraInterface interface + + spectroscopy_camera_dummy: + module.Class: 'spectroscopy_camera_dummy.Main' + """ + + def on_activate(self): + """ Activate module """ + self._build_constraints() + + self._acquisition_timer = QtCore.QTimer() + self._acquisition_timer.setSingleShot(True) + self._acquisition_timer.timeout.connect(self._end_acquisition, QtCore.Qt.QueuedConnection) + + self._acquired_data = None + self._read_mode = ReadMode.FVB + self._readout_speed = self.get_constraints().read_modes[0] + self._active_tracks = [] + self._image_advanced_parameters = None + self._gain = self.get_constraints().internal_gains[0] + self._exposure = 1 + self._trigger_mode = self.get_constraints().trigger_modes[0] + + self._shutter_open_state = True + self._cooler_on = True + self._temperature_setpoint = 183 # ~-90°C + + def on_deactivate(self): + """ Deactivate module """ + pass + + def get_constraints(self): + """ Returns all the fixed parameters of the hardware which can be used by the logic. + + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware + """ + return self._constraints + + def _build_constraints(self): + """ Internal method that build the constraints once at initialisation + + This makes multiple call to the DLL, so it will be called only onced by on_activate + """ + constraints = Constraints() + constraints.name = 'Spectroscopy camera dummy' + constraints.width, constraints.width = 1024, 256 + constraints.pixel_size_width, constraints.pixel_size_width = 13e-6, 13e-6 + constraints.internal_gains = [1, 2, 4] + constraints.readout_speeds = [50000, 1000000, 3000000] + constraints.has_cooler = True + constraints.trigger_modes = ['Internal', 'Dummy TTL'] + constraints.has_shutter = True + constraints.read_modes = [ReadMode.FVB, ReadMode.MULTIPLE_TRACKS, ReadMode.IMAGE, ReadMode.IMAGE_ADVANCED] + self._constraints = constraints + + ############################################################################## + # Basic functions + ############################################################################## + def start_acquisition(self): + """ Starts an acquisition of the current mode and returns immediately """ + self._acquired_data = None + self.module_state.lock() + self._acquisition_timer.start() + + def _end_acquisition(self): + """ Function called when the dummy acquisition is finished (after the exposure time) """ + self.module_state.unlock() + if self.get_read_mode() == ReadMode.FVB: + self._acquired_data = self._get_fake_spectra() + elif self.get_read_mode() == ReadMode.MULTIPLE_TRACKS: + self._acquired_data = [self._get_fake_spectra() for track in self.get_active_tracks()] + elif self.get_read_mode() == ReadMode.IMAGE: + self._acquired_data = self._get_fake_image() + elif self.get_read_mode() == ReadMode.IMAGE_ADVANCED: + self._acquired_data = self._get_fake_image(image_advanced=True) + + def _get_fake_spectra(self): + """ Function that build fake a spectrum """ + width = self.get_constraints().width + data = np.random.uniform(0, 10, width) # constant noise + data += np.random.uniform(0, 1 * self.get_exposure_time(), width) # noise linear with time + if self.get_shutter_open_state(): + data += np.random.uniform(0, 0.2 * self.get_exposure_time(), width) # noise for background + number_of_peaks = round(np.random.uniform(0, 20)) + peak_position = np.random.uniform(0, width, number_of_peaks) + peak_linewidth = np.random.uniform(1, 5, number_of_peaks) + peak_intensity = np.random.uniform(0, 20*self.get_exposure_time(), number_of_peaks) + x = np.arange(width) + for i in range(number_of_peaks): + data += peak_intensity[i] * np.exp(-(x-peak_position[i])**2/(2*peak_linewidth[i]**2)) + data *= self.get_gain() + return data + + def _get_fake_image(self, image_advanced=False, line_width_height=20): + """ Function that build fake a image data """ + height = self.get_constraints().height + spectra = self._get_fake_spectra() + width = len(spectra) + x = np.arange(height) + intensity = np.exp(-(x-height/2)**2/(2*line_width_height**2)) + data = np.random.uniform(0.1, 1, (width, height)) + image = (data * intensity).T * spectra + if image_advanced: + params = self.get_image_advanced_parameters() + image = image[params.vertical_start:params.vertical_end, params.horizontal_start:params.horizontal_end] + image = image[::params.vertical_binning, ::params.horizontal_binning] + return image + + def abort_acquisition(self): + """ Abort acquisition """ + self._acquisition_timer.stop() + self.module_state.unlock() + + def get_ready_state(self): + """ Get the status of the camera, to know if the acquisition is finished or still ongoing. + + @return (bool): True if the camera is ready, False if an acquisition is ongoing + + As there is no synchronous acquisition in the interface, the logic needs a way to check the acquisition state. + """ + return self.module_state() == 'idle' + + def get_acquired_data(self): + """ Return an array of last acquired data. + + @return: Data in the format depending on the read mode. + + Depending on the read mode, the format is : + 'FVB' : 1d array + 'MULTIPLE_TRACKS' : list of 1d arrays + 'IMAGE' 2d array of shape (width, height) + 'IMAGE_ADVANCED' 2d array of shape (width, height) + + Each value might be a float or an integer. + """ + return self._acquired_data + + ############################################################################## + # Read mode functions + ############################################################################## + def get_read_mode(self): + """ Getter method returning the current read mode used by the camera. + + @return (ReadMode): Current read mode + """ + return self._read_mode + + def set_read_mode(self, value): + """ Setter method setting the read mode used by the camera. + + @param (ReadMode) value: read mode to set + """ + if value in self.get_constraints().read_modes: + self._read_mode = value + + ############################################################################## + # Readout speed functions + ############################################################################## + def get_readout_speed(self): + """ Get the current readout speed of the camera + + This value is one of the possible values given by constraints + """ + return self._readout_seed + + def set_readout_speed(self, value): + """ Set the readout speed of the camera + + @param (float) value: Readout speed to set, must be a value from the constraints readout_speeds list + """ + if value in self.get_constraints().readout_speeds: + self._readout_speed = value + + ############################################################################## + # Active tracks functions + # + # Method used only for read mode MULTIPLE_TRACKS + ############################################################################## + def get_active_tracks(self): + """ Getter method returning the read mode tracks parameters of the camera. + + @return (list): active tracks positions [(start_1, end_1), (start_2, end_2), ... ] + + Should only be used while in MULTIPLE_TRACKS mode + """ + return self._active_tracks + + def set_active_tracks(self, value): + """ Setter method for the active tracks of the camera. + + @param (list) value: active tracks positions as [(start_1, end_1), (start_2, end_2), ... ] + + Some camera can sum the signal over tracks of pixels (all width times a height given by start and stop pixels) + This sum is done internally before the analog to digital converter to reduce the signal noise. + + Should only be used while in MULTIPLE_TRACKS mode + """ + self._active_tracks = value + + ############################################################################## + # Image advanced functions + # + # Method used only for read mode IMAGE_ADVANCED + ############################################################################## + def get_image_advanced_parameters(self): + """ Getter method returning the image parameters of the camera. + + @return (ImageAdvancedParameters): Current image advanced parameters + + Should only be used while in IMAGE_ADVANCED mode + """ + return self._image_advanced_parameters + + def set_image_advanced_parameters(self, value): + """ Setter method setting the read mode image parameters of the camera. + + @param (ImageAdvancedParameters) value: Parameters to set + + Should only be used while in IMAGE_ADVANCED mode + """ + if not isinstance(value, ImageAdvancedParameters): + self.log.error('ImageAdvancedParameters value error. Value {} is not correct.'.format(value)) + self._image_advanced_parameters = value + + ############################################################################## + # Gain mode functions + ############################################################################## + def get_gain(self): + """ Get the current gain. + + @return (float): Current gain + + Gain value should be one in the constraints internal_gains list. + """ + return self._gain + + def set_gain(self, value): + """ Set the gain. + + @param (float) value: New gain, value should be one in the constraints internal_gains list. + """ + if value in self.get_constraints().internal_gains: + self._gain = value + + ############################################################################## + # Exposure functions + ############################################################################## + def get_exposure_time(self): + """ Get the exposure time in seconds + + @return: (float) exposure time + """ + return self._exposure + + def set_exposure_time(self, value): + """ Set the exposure time in seconds. + + @param value: (float) desired new exposure time + + @return: nothing + """ + if value > 0: + self._exposure = value + self.acquisition_timer.setInterval(value*1e3) + + ############################################################################## + # Trigger mode functions + ############################################################################## + def get_trigger_mode(self): + """ Getter method returning the current trigger mode used by the camera. + + @return (str): Trigger mode + + This string should match one in the constraints trigger_modes list. + """ + return self._trigger_mode + + def set_trigger_mode(self, value): + """ Setter method for the trigger mode used by the camera. + + @param (str) value: trigger mode, should match one in the constraints trigger_modes list. + """ + if value in self.get_constraints().trigger_modes: + self._trigger_mode = value + + ############################################################################## + # Shutter mode function + # + # Method used only if constraints.has_shutter + ############################################################################## + def get_shutter_open_state(self): + """ Getter method returning the shutter mode. + + @return (bool): True if the shutter is open, False of closed + """ + return self._shutter_open_state + + def set_shutter_open_state(self, value): + """ Setter method setting the shutter mode. + + @param (bool) value: True to open, False tp close + """ + if isinstance(value, bool): + self._shutter_open_state = value + + ############################################################################## + # Temperature functions + # + # Method used only if constraints.has_cooler + ############################################################################## + def get_cooler_on(self): + """ Getter method returning the cooler status + + @return (bool): True if the cooler is on + """ + return self._cooler_on + + def set_cooler_on(self, value): + """ Setter method for the the cooler status + + @param (bool) value: True to turn it on, False to turn it off + """ + if isinstance(value, bool): + self._cooler_on = value + + def get_temperature(self): + """ Getter method returning the temperature of the camera in Kelvin. + + @return (float) : Measured temperature in kelvin + """ + return self._temperature_setpoint + + def get_temperature_setpoint(self): + """ Getter method for the temperature setpoint of the camera. + + @return (float): Current setpoint in Kelvin + """ + return self._temperature_setpoint + + def set_temperature_setpoint(self, value): + """ Setter method for the temperature setpoint of the camera. + + @param (float) value: New setpoint in Kelvin + """ + if value > 0: + self._temperature_setpoint = value From 7c2eb6c0dfd9d73d4d3c25fb0a05b7df31314f7a Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 11:49:44 +0200 Subject: [PATCH 34/49] small check in newton --- hardware/camera/andor/Newton_940.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index c78cd8a239..d2cc5f5a56 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -406,6 +406,8 @@ def set_image_advanced_parameters(self, value): Should only be used while in IMAGE_ADVANCED mode """ + if not isinstance(value, ImageAdvancedParameters): + self.log.error('ImageAdvancedParameters value error. Value {} is not correct.'.format(value)) self._image_advanced_parameters = value self._update_image() From a0ebcf3e61ade76aa4f533ab35c35760411e2d5b Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 11:50:12 +0200 Subject: [PATCH 35/49] started working on v2 of spectro interface --- interface/spectrometer_complete_interface.py | 85 +++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index dd4879bb38..0b0794a938 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -18,87 +18,92 @@ Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the top-level directory of this distribution and at """ +from enum import Enum from core.interface import abstract_interface_method from core.meta import InterfaceMetaclass +from core.interface import ScalarConstraint -class SpectrometerInterface(metaclass=InterfaceMetaclass): - """ - This is the Interface class to define the controls for spectrometer hardware - """ +class Grating: + """ Class defining formally a hardware grating """ + def __init__(self): + self.ruling = None # Ruling in line per meter + self.blaze = None # Blaze in meter + self.wavelength_constraints = ScalarConstraint(unit='m') # Wavelength limits in meter - @abstract_interface_method - def get_constraints(self): - """Returns all the fixed parameters of the hardware which can be used by the logic. - @return: (dict) constraint dict : { +class Port(Enum): + """ Class defining the possible port for input or output """ + FRONT = 0 + SIDE = 1 - 'optical_parameters' : (tuple) (focal_length, angular_deviation, focal_tilt) - focal_length : focal length in m - angular_deviation : angular deviation in rad - focal_tilt : focal tilt in rad - give the optical parameters (in s.i) used to measure the wavelength dispersion of the spectrometer, - 'gratings_info' : (list) [(tuple) (ruling, blaze), ..] give the gratings info for any gratings installed - with position corresponding to grating index, +class Constraints: + """ Class defining formally the hardware constraints """ + def __init__(self): + self.focal_length = None # Focal length in meter + self.angular_deviation = None # Angular deviation in radian + self.focal_tilt = None # Focal tilt in radian + self.gratings = [] # List of Grating object + self.has_side_input = False # Tells if the hardware has an second input on the side + self.has_side_output = False # Tells if the hardware has an second output on the side + self.input_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None + self.output_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None + self.shutter_modes = [] # Hardware defined shutter modes (list of string) - 'number_of_gratings' : (int) give the number of gratings installed (ex:3), - 'wavelength_limits' : (list) [[(float) wavelength_min, (float) wavelength_max], .. ] give the list of - the wavelength limits for any gratings installed with position corresponding to grating index, +class SpectrometerInterface(metaclass=InterfaceMetaclass): + """ This is the interface class to define the controls for spectrometer hardware - 'available_port' : (list) [[(int) input port, ..], [(int) output port, ..]] give the available - input (1st list) and output (2nd port) ports in the spectrometer, + This interface only deals with the part of the spectrometer that set central wavelength and gratings. + For the parameter of the spectroscopy camera, see the "spectroscopy_camera_interface". + """ - 'auto_slit_installed' : (list) [[(bool) input slit installed, ..], [(bool) output slit installed, ..]] - give if the related input (1st list) and output (2nd list ) ports has motorized auto slit installed. + @abstract_interface_method + def get_constraints(self): + """ Returns all the fixed parameters of the hardware which can be used by the logic. - (optional) : let this key empty if no shutter is installed ! - 'shutter_modes' : (list) [(str) shutter_mode, ..] give the shutter modes available if any - shutter is installed. - } + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware """ pass ############################################################################## # Gratings functions ############################################################################## - @abstract_interface_method - def get_grating_number(self): - """Returns the current grating identification (0 to self.get_number_gratings-1) + def get_grating_index(self): + """ Returns the current grating index + + @return (int): Current grating index """ pass @abstract_interface_method - def set_grating_number(self, grating): - """Sets the required grating (0 to self.get_number_gratings-1) + def set_grating_index(self, value): + """ Sets the grating by index - @param (int) grating: grating identification number - @return: void + @param (int) value: grating index """ pass ############################################################################## # Wavelength functions ############################################################################## - @abstract_interface_method def get_wavelength(self): - """Returns the central current wavelength (m) + """ Returns the current central wavelength in meter - @return (float) wavelength (m) + @return (float): current central wavelength (meter) """ pass @abstract_interface_method - def set_wavelength(self, wavelength): - """Sets the new central wavelength (m) + def set_wavelength(self, value): + """ Sets the new central wavelength in meter - @params (float) wavelength (m) + @params (float) value: The new central wavelength (meter) """ - pass ############################################################################## From 13a32522027a95e6b43d0c80281ab5c66126aed6 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 12:11:00 +0200 Subject: [PATCH 36/49] fixed bug in dummy and added it to default config --- config/example/default.cfg | 3 +++ hardware/spectroscopy_camera_dummy.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/example/default.cfg b/config/example/default.cfg index be513c2f80..8535d4ac0e 100644 --- a/config/example/default.cfg +++ b/config/example/default.cfg @@ -83,6 +83,9 @@ hardware: cameradummy: module.Class: 'camera.camera_dummy.CameraDummy' + + spectroscopy_camera_dummy: + module.Class: 'spectroscopy_camera_dummy.Main' logic: simpledatalogic: module.Class: 'simple_data_logic.SimpleDataLogic' diff --git a/hardware/spectroscopy_camera_dummy.py b/hardware/spectroscopy_camera_dummy.py index aeafa5634b..15bb12ac1a 100644 --- a/hardware/spectroscopy_camera_dummy.py +++ b/hardware/spectroscopy_camera_dummy.py @@ -73,7 +73,7 @@ def _build_constraints(self): """ constraints = Constraints() constraints.name = 'Spectroscopy camera dummy' - constraints.width, constraints.width = 1024, 256 + constraints.width, constraints.height = 1024, 256 constraints.pixel_size_width, constraints.pixel_size_width = 13e-6, 13e-6 constraints.internal_gains = [1, 2, 4] constraints.readout_speeds = [50000, 1000000, 3000000] @@ -291,7 +291,7 @@ def set_exposure_time(self, value): """ if value > 0: self._exposure = value - self.acquisition_timer.setInterval(value*1e3) + self._acquisition_timer.setInterval(value*1e3) ############################################################################## # Trigger mode functions From e291ad4c6e549793c7623b173985deaa64140a08 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 18:37:02 +0200 Subject: [PATCH 37/49] correct shutter architecture error in spectro camera_interface --- hardware/camera/andor/Newton_940.py | 56 +++++++--------------- interface/spectroscopy_camera_interface.py | 25 +++++++--- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index d2cc5f5a56..4a0f734e40 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -32,23 +32,10 @@ from core.configoption import ConfigOption from interface.spectroscopy_camera_interface import SpectroscopyCameraInterface -from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters +from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters, ShutterState # Bellow are the classes used by Andor dll. They are not par of Qudi interfaces -class ReadModeDLL(Enum): - """ Class defining the possible read mode supported by Andor DLL - - This read mode is different from the class of the interface, be careful! - Only FVB, RANDOM_TRACK and IMAGE are used by this module. - """ - FVB = 0 - MULTI_TRACK = 1 - RANDOM_TRACK = 2 - SINGLE_TRACK = 3 - IMAGE = 4 - - class AcquisitionMode(Enum): """ Class defining the possible acquisition mode supported by Andor DLL @@ -71,13 +58,6 @@ class TriggerMode(Enum): EXTERNAL_CHARGE_SHIFTING = 12 -class ShutterMode(Enum): - """ Class defining the possible shutter mode supported by Andor DLL """ - AUTO = 0 - OPEN = 1 - CLOSE = 2 - - OK_CODE = 20002 # Status code associated with DRV_SUCCESS # Error codes and strings defines by the DLL @@ -131,13 +111,10 @@ class Main(Base, SpectroscopyCameraInterface): - Newton 940 """ _dll_location = ConfigOption('dll_location', missing='error') - _close_shutter_on_deactivate = ConfigOption('close_shutter_on_deactivate', False) - # todo: open shutter_on_activate ? _start_cooler_on_activate = ConfigOption('start_cooler_on_activate', True) _default_temperature = ConfigOption('default_temperature', 260) _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') - _max_exposure_time = ConfigOption('max_exposure_time', 600) # todo: does this come from the dll and why forbid it ? _shutter_TTL = ConfigOption('shutter_TTL', 1) # todo: explain what this is for the user _shutter_switching_time = ConfigOption('shutter_switching_time', 100e-3) # todo: explain what this is for the user @@ -216,7 +193,7 @@ def _check(self, func_val): def _build_constraints(self): """ Internal method that build the constraints once at initialisation - This makes multiple call to the DLL, so it will be called only onced by on_activate + This makes multiple call to the DLL, so it will be called only once by on_activate """ constraints = Constraints() constraints.name = self._get_name() @@ -254,7 +231,7 @@ def abort_acquisition(self): """ Aborts the acquisition """ self._check(self._dll.AbortAcquisition()) - def get_ready_state(self): # todo: check this function, i've guessed the dll behavior... + def get_ready_state(self): # todo: test this function, i've guessed the dll behavior... """ Get the status of the camera, to know if the acquisition is finished or still ongoing. @return (bool): True if the camera is ready, False if an acquisition is ongoing @@ -327,12 +304,12 @@ def set_read_mode(self, value): self.log.error('read_mode not supported') return - conversion_dict = {ReadMode.FVB: ReadModeDLL.FVB, - ReadMode.MULTIPLE_TRACKS: ReadModeDLL.RANDOM_TRACK, - ReadMode.IMAGE: ReadModeDLL.IMAGE, - ReadMode.IMAGE_ADVANCED: ReadModeDLL.IMAGE} + conversion_dict = {ReadMode.FVB: 0, + ReadMode.MULTIPLE_TRACKS: 2, + ReadMode.IMAGE: 4, + ReadMode.IMAGE_ADVANCED: 4} - n_mode = conversion_dict[value].value + n_mode = conversion_dict[value] status_code = self._check(self._dll.SetReadMode(n_mode)) if status_code == OK_CODE: self._read_mode = value @@ -523,24 +500,25 @@ def set_trigger_mode(self, value): ############################################################################## # Shutter mode functions ############################################################################## - def get_shutter_open_state(self): - """ Getter method returning the shutter mode. + def get_shutter_state(self): + """ Getter method returning the shutter state. - @return (bool): True if the shutter is open, False of closed + @return (ShutterState): The current shutter state """ if not self.get_constraints().has_shutter: self.log.error('Can not get state of the shutter, camera does not have a shutter') return self._shutter_status # todo from hardware - def set_shutter_open_state(self, value): - """ Setter method setting the shutter mode. + def set_shutter_state(self, value): + """ Setter method setting the shutter state. - @param (bool) value: True to open, False tp close + @param (ShutterState) value: the shutter state to set """ if not self.get_constraints().has_shutter: self.log.error('Can not set state of the shutter, camera does not have a shutter') - mode = ShutterMode.OPEN if value else ShutterMode.CLOSE - mode = ct.c_int(mode.value) # todo: needed for interger ? + + conversion_dict = {ShutterState.AUTO: 0, ShutterState.OPEN: 1, ShutterState.CLOSED: 2} + mode = conversion_dict[value] shutter_TTL = int(self._shutter_TTL) shutter_time = int(round(self._shutter_switching_time*1e3)) # DLL use ms status_code = self._check(self._dll.SetShutter(shutter_TTL, mode, shutter_time, shutter_time)) diff --git a/interface/spectroscopy_camera_interface.py b/interface/spectroscopy_camera_interface.py index a4d4a6a0b3..efc2ef49fe 100644 --- a/interface/spectroscopy_camera_interface.py +++ b/interface/spectroscopy_camera_interface.py @@ -40,6 +40,19 @@ class ReadMode(Enum): IMAGE_ADVANCED = 3 +class ShutterState(Enum): + """ Class defining the possible shutter states + + AUTO means the shutter opens only for the acquisition time. + + Shutter might be handled by the camera or the grating spectrometer. + As a consequence, both interfaces have the shutter features. + """ + CLOSED = 0 + OPEN = 1 + AUTO = 4 # Value do not conflict with ShutterState from simple_laser_logic + + class Constraints: """ Class defining formally the hardware constraints """ def __init__(self): @@ -278,18 +291,18 @@ def set_trigger_mode(self, value): # Method used only if constraints.has_shutter ############################################################################## @abstract_interface_method - def get_shutter_open_state(self): - """ Getter method returning the shutter mode. + def get_shutter_state(self): + """ Getter method returning the shutter state. - @return (bool): True if the shutter is open, False of closed + @return (ShutterState): The current shutter state """ pass @abstract_interface_method - def set_shutter_open_state(self, value): - """ Setter method setting the shutter mode. + def set_shutter_state(self, value): + """ Setter method setting the shutter state. - @param (bool) value: True to open, False tp close + @param (ShutterState) value: the shutter state to set """ pass From d33ee008ff4bd7ac8e44a32a8c6b60e058861070 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 18:37:34 +0200 Subject: [PATCH 38/49] v2 of the spectrometer complete interface --- interface/spectrometer_complete_interface.py | 132 +++++++++---------- 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 0b0794a938..2452929bb8 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -26,19 +26,47 @@ class Grating: - """ Class defining formally a hardware grating """ + """ Class defining formally the grating constraints """ def __init__(self): self.ruling = None # Ruling in line per meter self.blaze = None # Blaze in meter - self.wavelength_constraints = ScalarConstraint(unit='m') # Wavelength limits in meter + self.wavelength_max = None # Wavelength limits in meter -class Port(Enum): - """ Class defining the possible port for input or output """ +class PortType(Enum): + """ Class defining the possible type : input or output""" + INPUT = 0 + OUTPUT = 1 + + +class PortSide(Enum): + """ Class defining the possible input/output port side """ FRONT = 0 SIDE = 1 +class Port: + """ Class defining formally the port constraints """ + def __init__(self): + self.type = PortType.INPUT + self.side = PortSide.FRONT + self.is_motorized = True + self.constraints = ScalarConstraint(unit='m') + + +class ShutterState(Enum): + """ Class defining the possible shutter states + + AUTO means the shutter opens only for the acquisition time. + + Shutter might be handled by the camera or the grating spectrometer. + As a consequence, both interfaces have the shutter features. + """ + CLOSED = 0 + OPEN = 1 + AUTO = 4 # Value do not conflict with ShutterState from simple_laser_logic + + class Constraints: """ Class defining formally the hardware constraints """ def __init__(self): @@ -46,11 +74,8 @@ def __init__(self): self.angular_deviation = None # Angular deviation in radian self.focal_tilt = None # Focal tilt in radian self.gratings = [] # List of Grating object - self.has_side_input = False # Tells if the hardware has an second input on the side - self.has_side_output = False # Tells if the hardware has an second output on the side - self.input_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None - self.output_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None - self.shutter_modes = [] # Hardware defined shutter modes (list of string) + self.ports = [] # List of Ports object + self.has_shutter = False # If the hardware has shutter interfaced by this module class SpectrometerInterface(metaclass=InterfaceMetaclass): @@ -111,95 +136,62 @@ def set_wavelength(self, value): ############################################################################## @abstract_interface_method - def get_input_port(self): - """Returns the current port for the input flipper mirror. - - @return: (int) 0 is for front port, 1 is for side port - in case of no flipper mirror, front port (0) is used - """ - pass - - @abstract_interface_method - def set_input_port(self, input_port): - """Sets the input port - 0 is for front port, 1 is for side port + def get_current_port(self, port_type): + """ Returns the current port side on input or output - @param input_port: (int). has to be 0 or 1 - @return: nothing - """ - pass - - @abstract_interface_method - def get_output_port(self): - """Returns the current port for the output flipper mirror. + @param (PortType) port_type: input or output - @return: (int) 0 is for front port, 1 is for side port - in case of no flipper mirror, front port (0) is used + @return (PortSide): current port side """ pass @abstract_interface_method - def set_output_port(self, output_port): - """Sets the input port - 0 is for front port, 1 is for side port + def set_current_port(self, port_type, value): + """ Set the current port on input or output - @param output_port: (int). has to be 0 or 1 - @return: nothing + @param (PortType) port_type: input or output + @param (PortSide) value: The port side to set """ pass @abstract_interface_method - def get_input_slit_width(self): - """Returns the input slit width (um) of the current input slit. + def get_slit_width(self, port_type, port_side): + """ Getter for the current slit width in meter on a given port - @return: (int) offset - slit width, unit is meter (SI) - """ - pass - - @abstract_interface_method - def set_input_slit_width(self, slit_width): - """Sets the new slit width for the current input slit. + @param (PortType) port_type: input or output + @param (PortSide) port_side: front or side - @param slit_width: (float) slit width unit is meter (SI) - :return: nothing + @return (float): input slit width (in meter) """ pass @abstract_interface_method - def get_output_slit_width(self): - """Returns the output slit width (um) of the current output slit. + def set_slit_width(self, port_type, port_side, value): + """ Setter for the input slit width in meter - @return: (int) offset - slit width, unit is meter (SI) - """ - pass - - @abstract_interface_method - def set_output_slit_width(self, slit_width): - """Sets the new slit width for the current output slit. - - @param slit_width: (float) slit width unit is meter (SI) - :return: nothing + @param (PortType) port_type: input or output + @param (PortSide) port_side: front or side + @param (float) value: input slit width (in meter) """ pass ############################################################################## - # Shutter mode function (optional) + # Shutter mode function + # + # Method used only if constraints.has_shutter ############################################################################## - # Shutter mode function are used in logic only if the spectrometer constraints - # dictionary has 'shutter_modes' key filled. If empty this functions will not - # be used and can be ignored. - @abstract_interface_method - def get_shutter_status(self): - """Getter method returning the shutter mode. + def get_shutter_state(self): + """ Getter method returning the shutter state. - @return: (str) shutter mode (must be compared to the list) + @return (ShutterState): The current shutter state """ pass @abstract_interface_method - def set_shutter_status(self, shutter_mode): - """Setter method setting the shutter mode. + def set_shutter_state(self, value): + """ Setter method setting the shutter state. - @param shutter_mode: (str) shutter mode (must be compared to the list) - @return: nothing + @param (ShutterState) value: the shutter state to set """ - pass + pass \ No newline at end of file From 65ef867e96cea8afa4f5ff7d07caad8486820b17 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Tue, 5 May 2020 19:31:01 +0200 Subject: [PATCH 39/49] started the v2 of the shamrock, WIP --- hardware/spectrometer/shamrock.py | 207 +++++++++++++++++++----------- 1 file changed, 130 insertions(+), 77 deletions(-) diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 97cdb0399e..2817d6b11f 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """ -This module contains the hardware module of the Shamrock 500 -spectrometer from Andor. +This module interface Shamrock spectrometer from Andor. Qudi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,17 +19,15 @@ top-level directory of this distribution and at na=not applicable """ +import os +import numpy as np +import ctypes as ct from core.module import Base -from interface.spectrometer_complete_interface import SpectrometerInterface from core.configoption import ConfigOption -from core.util.modules import get_main_dir -import os -import numpy as np - -import ctypes as ct -import time +from interface.spectrometer_complete_interface import SpectrometerInterface +from interface.spectrometer_complete_interface import Grating, Port, Constraints ERROR_CODE = { 20201: "SHAMROCK_COMMUNICATION_ERROR", @@ -43,20 +40,132 @@ 20275: "SHAMROCK_NOT_INITIALIZED" } +OK_CODE = 20202 # Status code associated with DRV_SUCCESS + + class Shamrock(Base,SpectrometerInterface): + """ Hardware module that interface a Shamrock spectrometer from Andor + + Tested with : + - Shamrock 500 + """ -# default values _dll_location = ConfigOption('dll_location', missing='error') + _serial_number = ConfigOption('serial_number', None) # Optional - if Shamrock hardware are connected + + SLIT_MIN_WIDTH = 10e-6 # todo: can this be get from the DLL ? Or else is it the same for ALL spectro ? If not, maybe a config option ? + SLIT_MAX_WIDTH = 2500e-6 # todo: same + + # Declarations of attributes to make Pycharm happy + def __init__(self): + self._constraints = None + self._dll = None + self._shutter_status = None + self._device_id = None + +############################################################################## +# Basic functions +############################################################################## + + def on_activate(self): + """ Activate module """ + try: + self._dll = ct.cdll.LoadLibrary(self._dll_location) + except OSError: + self.log.error('Error during dll loading of the Shamrock spectrometer, check the dll path.') + + status_code = self._dll.ShamrockInitialize() + if status_code != OK_CODE: + self.log.error('Problem during Shamrock initialization') + return + + if self._serial_number is not None: + # Check that the right hardware is connected and connect to the right one + devices = self._get_connected_devices() + target = str(self._serial_number) + if target in devices: + self._device_id = devices.index(target) + else: + self.log.error('Serial number {} not found in connected devices : {}'.format(target, devices)) + return + else: + self._device_id = 0 + + self._constraints = self._build_constraints() - _width = 2048 #number of pixels along dispersion axis - _height = 512 #number of pixels (perpendicular to dispersion axis) - _pixelwidth = 13E-6 #unit is meter - _pixelheight = 13E-6 #unit is meter - MAX_SLITS=4 - MAX_GRATINGS=3 - SLIT_MIN_WIDTH=10E-6 - SLIT_MAX_WIDTH=2500E-6 + + self.gratingID = self.get_grating_number() + self.set_number_of_pixels(self._width) + self.set_pixel_width(self._pixelwidth) + + def on_deactivate(self): + return self.dll.ShamrockClose() + + def _get_connected_devices(self): + """ Return a list of serial numbers of the connected devices + + @result (list(str)): A list of the serial numbers as string """ + result = [] + for i in range(self._get_number_devices()): + result.append(self._get_device_serial_number(i)) + return result + + def _get_device_serial_number(self, index): + """ Return the serial number of a hardware by the index number + + @param (int) index: The index the hardware + + @result (str): The serial number as a string + """ + # todo: This function + return 'Please fix me !' + + def _build_constraints(self): + """ Internal method that build the constraints once at initialisation + + This makes multiple call to the DLL, so it will be called only once by on_activate + """ + constraints = Constraints() + + optical_param = self._get_optical_parameters() + constraints.focal_length = optical_param['focal_length'] + constraints.angular_deviation = optical_param['angular_deviation'] + constraints.focal_tilt = optical_param['focal_tilt'] + + number_of_gratings = self._get_number_gratings() + for i in range(number_of_gratings): + grating_info = self._get_grating_info(i) + grating = Grating() + grating.ruling = grating_info['ruling'] + grating.blaze = grating_info['blaze'] + grating.wavelength_max = self._get_wavelength_limit(i) + constraints.gratings.append(grating) + + # todo: below this is a war zone + self.has_side_input = bool(self._flipper_mirror_is_present(1)) + self.has_side_output = bool(self._flipper_mirror_is_present(2)) + + + return constraints + + + self.focal_length = None # Focal length in meter + self.angular_deviation = None # Angular deviation in radian + self.focal_tilt = None # Focal tilt in radian + self.gratings = [] # List of Grating object + self.has_side_input = False # Tells if the hardware has an second input on the side + self.has_side_output = False # Tells if the hardware has an second output on the side + self.input_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None + self.output_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None + self.shutter_modes = [] # Hardware defined shutter modes (list of string) + + def get_constraints(self): + """ Returns all the fixed parameters of the hardware which can be used by the logic. + + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware + """ + return self._constraints def get_constraints(self): """Returns all the fixed parameters of the hardware which can be used by the logic. @@ -108,38 +217,6 @@ def get_constraints(self): } return constraint_dict -############################################################################## -# Basic functions -############################################################################## - - def on_activate(self): - """ Activate module. - """ - self.spectrometer_status = 0 - self.errorcode = self._create_errorcode() - - #self.dll = ct.cdll.LoadLibrary('C:/temp/ShamrockCIF.dll') - - self.dll = ct.cdll.LoadLibrary(self._dll_location) - - code = self.dll.ShamrockInitialize() - - if code != 20202: - self.log.info('Problem during spectrometer initialization') - self.on_deactivate() - else: - self.spectrometer_status = 1 - nd = ct.c_int() - self.dll.ShamrockGetNumberDevices(ct.byref(nd)) - #self.nd = nd.value - self.deviceID = 0 #hard coding : whatever the number of devices... we work with the first. Fix me ? - self.gratingID = self.get_grating_number() - self.set_number_of_pixels(self._width) - self.set_pixel_width(self._pixelwidth) - - def on_deactivate(self): - return self.dll.ShamrockClose() - def check(self, func_val): """ Check routine for the received error codes. """ @@ -149,37 +226,13 @@ def check(self, func_val): '{1}'.format(func_val, self.errorcode[func_val])) return func_val - def _create_errorcode(self): - """ Create a dictionary with the errorcode for the device. - """ - - maindir = get_main_dir() - - filename = os.path.join(maindir, 'hardware', 'spectrometer', 'errorcodes_shamrock.h') - try: - with open(filename) as f: - content = f.readlines() - except: - self.log.error('No file "errorcodes_shamrock.h" could be found in the ' - 'hardware/spectrometer directory!') - errorcode = {} - for line in content: - if '#define SHAMROCK' in line: - errorstring, errorvalue = line.split()[-2:] - errorcode[int(errorvalue)] = errorstring - - return errorcode - - def get_number_device(self): - """ - Returns the number of devices - Tested : yes - """ + def _get_number_devices(self): + """ Returns the number of devices """ number_of_devices = ct.c_int() self.check(self.dll.ShamrockGetNumberDevices(self.deviceID, ct.byref(number_of_devices))) return number_of_devices.value - def get_optical_parameters(self): + def _get_optical_parameters(self): """ Returns the spectrometer optical parameters From 7539ec2e2f9b9a6fc29cd5736d505df824a29d6e Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Wed, 6 May 2020 10:46:36 +0200 Subject: [PATCH 40/49] modified the grating spetro interface for simplicity --- interface/spectrometer_complete_interface.py | 80 +++++++------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 2452929bb8..9eeae84a0c 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -35,38 +35,20 @@ def __init__(self): class PortType(Enum): """ Class defining the possible type : input or output""" - INPUT = 0 - OUTPUT = 1 - - -class PortSide(Enum): - """ Class defining the possible input/output port side """ - FRONT = 0 - SIDE = 1 + INPUT_FRONT = 0 + INPUT_SIDE = 1 + OUTPUT_FRONT = 2 + OUTPUT_SIDE = 3 class Port: """ Class defining formally the port constraints """ def __init__(self): - self.type = PortType.INPUT - self.side = PortSide.FRONT + self.type = PortType.INPUT_FRONT self.is_motorized = True self.constraints = ScalarConstraint(unit='m') -class ShutterState(Enum): - """ Class defining the possible shutter states - - AUTO means the shutter opens only for the acquisition time. - - Shutter might be handled by the camera or the grating spectrometer. - As a consequence, both interfaces have the shutter features. - """ - CLOSED = 0 - OPEN = 1 - AUTO = 4 # Value do not conflict with ShutterState from simple_laser_logic - - class Constraints: """ Class defining formally the hardware constraints """ def __init__(self): @@ -136,62 +118,52 @@ def set_wavelength(self, value): ############################################################################## @abstract_interface_method - def get_current_port(self, port_type): - """ Returns the current port side on input or output - - @param (PortType) port_type: input or output + def get_input_port(self): + """ Returns the current input port - @return (PortSide): current port side + @return (PortType): current port side """ pass @abstract_interface_method - def set_current_port(self, port_type, value): - """ Set the current port on input or output + def set_input_port(self, value): + """ Set the current input port - @param (PortType) port_type: input or output - @param (PortSide) value: The port side to set + @param (PortType) value: The port side to set """ pass @abstract_interface_method - def get_slit_width(self, port_type, port_side): - """ Getter for the current slit width in meter on a given port - - @param (PortType) port_type: input or output - @param (PortSide) port_side: front or side + def get_output_port(self): + """ Returns the current output port - @return (float): input slit width (in meter) + @return (PortType): current port side """ pass @abstract_interface_method - def set_slit_width(self, port_type, port_side, value): - """ Setter for the input slit width in meter + def set_input_port(self, value): + """ Set the current output port - @param (PortType) port_type: input or output - @param (PortSide) port_side: front or side - @param (float) value: input slit width (in meter) + @param (PortType) value: The port side to set """ pass - ############################################################################## - # Shutter mode function - # - # Method used only if constraints.has_shutter - ############################################################################## @abstract_interface_method - def get_shutter_state(self): - """ Getter method returning the shutter state. + def get_slit_width(self, port_type): + """ Getter for the current slit width in meter on a given port + + @param (PortType) port_type: The port to inquire - @return (ShutterState): The current shutter state + @return (float): input slit width (in meter) """ pass @abstract_interface_method - def set_shutter_state(self, value): - """ Setter method setting the shutter state. + def set_slit_width(self, port_type, value): + """ Setter for the input slit width in meter - @param (ShutterState) value: the shutter state to set + @param (PortType) port_type: The port to set + @param (float) value: input slit width (in meter) """ pass \ No newline at end of file From dc031a08bb2dd5784514da77cfb895d65be2d3c1 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Wed, 6 May 2020 14:12:23 +0200 Subject: [PATCH 41/49] update grating spectro interface again --- interface/spectrometer_complete_interface.py | 7 +++---- interface/spectroscopy_camera_interface.py | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/interface/spectrometer_complete_interface.py b/interface/spectrometer_complete_interface.py index 9eeae84a0c..4c6474f694 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/spectrometer_complete_interface.py @@ -43,8 +43,8 @@ class PortType(Enum): class Port: """ Class defining formally the port constraints """ - def __init__(self): - self.type = PortType.INPUT_FRONT + def __init__(self, _type): + self.type = _type # PortType.INPUT_FRONT self.is_motorized = True self.constraints = ScalarConstraint(unit='m') @@ -57,7 +57,6 @@ def __init__(self): self.focal_tilt = None # Focal tilt in radian self.gratings = [] # List of Grating object self.ports = [] # List of Ports object - self.has_shutter = False # If the hardware has shutter interfaced by this module class SpectrometerInterface(metaclass=InterfaceMetaclass): @@ -142,7 +141,7 @@ def get_output_port(self): pass @abstract_interface_method - def set_input_port(self, value): + def set_output_port(self, value): """ Set the current output port @param (PortType) value: The port side to set diff --git a/interface/spectroscopy_camera_interface.py b/interface/spectroscopy_camera_interface.py index efc2ef49fe..ebaa178954 100644 --- a/interface/spectroscopy_camera_interface.py +++ b/interface/spectroscopy_camera_interface.py @@ -44,9 +44,6 @@ class ShutterState(Enum): """ Class defining the possible shutter states AUTO means the shutter opens only for the acquisition time. - - Shutter might be handled by the camera or the grating spectrometer. - As a consequence, both interfaces have the shutter features. """ CLOSED = 0 OPEN = 1 From 9d1eeacf89defa8e002877759ee680d512cc22e4 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Wed, 6 May 2020 14:12:44 +0200 Subject: [PATCH 42/49] update shamrock hardware --- hardware/spectrometer/shamrock.py | 804 ++++++++---------------------- 1 file changed, 220 insertions(+), 584 deletions(-) diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 2817d6b11f..0d708bdffd 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -19,7 +19,6 @@ top-level directory of this distribution and at na=not applicable """ -import os import numpy as np import ctypes as ct @@ -27,7 +26,7 @@ from core.configoption import ConfigOption from interface.spectrometer_complete_interface import SpectrometerInterface -from interface.spectrometer_complete_interface import Grating, Port, Constraints +from interface.spectrometer_complete_interface import Grating, PortType, Port, Constraints ERROR_CODE = { 20201: "SHAMROCK_COMMUNICATION_ERROR", @@ -42,6 +41,12 @@ OK_CODE = 20202 # Status code associated with DRV_SUCCESS +INPUT_CODE = 1 +OUTPUT_CODE = 2 + +FRONT_CODE = 0 +SIDE_CODE = 1 + class Shamrock(Base,SpectrometerInterface): """ Hardware module that interface a Shamrock spectrometer from Andor @@ -51,7 +56,7 @@ class Shamrock(Base,SpectrometerInterface): """ _dll_location = ConfigOption('dll_location', missing='error') - _serial_number = ConfigOption('serial_number', None) # Optional - if Shamrock hardware are connected + _serial_number = ConfigOption('serial_number', None) # Optional - needed if multiple Shamrock are connected SLIT_MIN_WIDTH = 10e-6 # todo: can this be get from the DLL ? Or else is it the same for ALL spectro ? If not, maybe a config option ? SLIT_MAX_WIDTH = 2500e-6 # todo: same @@ -66,7 +71,6 @@ def __init__(self): ############################################################################## # Basic functions ############################################################################## - def on_activate(self): """ Activate module """ try: @@ -93,14 +97,9 @@ def on_activate(self): self._constraints = self._build_constraints() - - - self.gratingID = self.get_grating_number() - self.set_number_of_pixels(self._width) - self.set_pixel_width(self._pixelwidth) - def on_deactivate(self): - return self.dll.ShamrockClose() + """ De-initialisation performed during deactivation of the module. """ + return self._dll.ShamrockClose() def _get_connected_devices(self): """ Return a list of serial numbers of the connected devices @@ -139,26 +138,33 @@ def _build_constraints(self): grating = Grating() grating.ruling = grating_info['ruling'] grating.blaze = grating_info['blaze'] - grating.wavelength_max = self._get_wavelength_limit(i) + grating.wavelength_max = self._get_wavelength_limit(i)[1] constraints.gratings.append(grating) - # todo: below this is a war zone - self.has_side_input = bool(self._flipper_mirror_is_present(1)) - self.has_side_output = bool(self._flipper_mirror_is_present(2)) + # Add the ports one by one + input_port_front = Port(PortType.INPUT_FRONT) + input_port_front.is_motorized = self._auto_slit_is_present('input', 'front') + constraints.ports.append(input_port_front) + if self.flipper_mirror_is_present('input'): + input_port_side = Port(PortType.INPUT_SIDE) + input_port_side.is_motorized = self._auto_slit_is_present('input', 'side') + constraints.ports.append(input_port_side) - return constraints + output_port_front = Port(PortType.OUTPUT_FRONT) + output_port_front.is_motorized = self._auto_slit_is_present('output', 'front') + constraints.ports.append(output_port_front) + if self.flipper_mirror_is_present('output'): + output_port_side = Port(PortType.OUTPUT_SIDE) + output_port_side.is_motorized = self._auto_slit_is_present('output', 'side') + constraints.ports.append(output_port_side) - self.focal_length = None # Focal length in meter - self.angular_deviation = None # Angular deviation in radian - self.focal_tilt = None # Focal tilt in radian - self.gratings = [] # List of Grating object - self.has_side_input = False # Tells if the hardware has an second input on the side - self.has_side_output = False # Tells if the hardware has an second output on the side - self.input_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None - self.output_motorized_slit = ScalarConstraint(unit='m') # Motorized slit constraints or None - self.shutter_modes = [] # Hardware defined shutter modes (list of string) + for port in constraints.ports: + port.constraints.min = self.SLIT_MIN_WIDTH + port.constraints.max = self.SLIT_MAX_WIDTH + + return constraints def get_constraints(self): """ Returns all the fixed parameters of the hardware which can be used by the logic. @@ -167,703 +173,333 @@ def get_constraints(self): """ return self._constraints - def get_constraints(self): - """Returns all the fixed parameters of the hardware which can be used by the logic. - - @return: (dict) constraint dict : { - - 'optical_parameters' : (dict) { - 'focal_length' : focal length in m, - 'angular_deviation' : angular deviation in rad, - 'focal_tilt' : focal tilt in rad} - give the optical parameters (in s.i) used to measure the wavelength dispersion of the spectrometer, - - 'gratings_info' : (list) [(tuple) (ruling, blaze), ..] give the gratings info for any gratings installed - with position corresponding to grating index, - - 'number_of_gratings' : (int) give the number of gratings installed (ex:3), + def _check(self, status_code): + """ Check routine for the received error codes. - 'wavelength_limits' : (list) [[(float) wavelength_min, (float) wavelength_max], .. ] give the list of - the wavelength limits for any gratings installed with position corresponding to grating index, + @param (int) status_code: The code returned by the DLL - 'available_port' : (list) [[(int) input port, ..], [(int) output port, ..]] give the available - input (1st list) and output (2nd port) ports in the spectrometer, + @return (int): The code given in parameter is returned """ + if status_code != OK_CODE: + self.log.error('Error in Shamrock with error code {}: {}'.format(status_code, ERROR_CODE[status_code])) + return status_code - 'auto_slit_installed' : (list) [[(bool) input slit installed, ..], [(bool) output slit installed, ..]] - give if the related input (1st list) and output (2nd list ) ports has motorized auto slit installed. + def _get_number_devices(self): + """ Returns the number of devices - (optional) : let this key empty if no shutter is installed ! - 'shutter_modes' : (list) [(str) shutter_mode, ..] give the shutter modes available if any - shutter is installed. - } - """ - optical_param = self.get_optical_parameters() - optical_param = (optical_param['focal_length'], optical_param['angular_deviation'], optical_param['focal_tilt']) - number_of_gratings = self.get_number_gratings() - gratings_info = [(self.get_grating_info(i)['ruling'], self.get_grating_info(i)['blaze']) for i in range(number_of_gratings)] - wavelength_limits = np.array([self.get_wavelength_limit(i) for i in range(number_of_gratings)]) - auto_slit_installed = np.array([[self.auto_slit_is_present('input',0), self.auto_slit_is_present('input',1)], - [self.auto_slit_is_present('output',0), self.auto_slit_is_present('output',1)]]) - input_port = [i for i in range(self.flipper_mirror_is_present(1)+1)] - output_port = [j for j in range(self.flipper_mirror_is_present(2)+1)] - available_port = np.array([input_port, output_port]) - constraint_dict = { - 'optical_parameters':optical_param , - 'gratings_info': gratings_info, - 'number_of_gratings': number_of_gratings, - 'wavelength_limits':wavelength_limits, - 'available_port': available_port, - 'auto_slit_installed':auto_slit_installed, - } - return constraint_dict - - def check(self, func_val): - """ Check routine for the received error codes. + @return (int): the number of devices detected by the DLL """ - - if not func_val == 20202: - self.log.error('Error in Shamrock with errorcode {0}:\n' - '{1}'.format(func_val, self.errorcode[func_val])) - return func_val - - def _get_number_devices(self): - """ Returns the number of devices """ number_of_devices = ct.c_int() - self.check(self.dll.ShamrockGetNumberDevices(self.deviceID, ct.byref(number_of_devices))) + self._check(self.dll.ShamrockGetNumberDevices(ct.byref(number_of_devices))) return number_of_devices.value def _get_optical_parameters(self): - """ - Returns the spectrometer optical parameters + """ Returns the spectrometer optical parameters - @return dictionnary { 'focal length (m)': float (m), - 'angular deviation (rad)': float (rad) - 'focal tilt (rad)': float (rad)} - Tested : yes - SI check : yes + @return (dict): A dictionary with keys 'focal_length', 'angular_deviation' and 'focal_tilt' + The unit of the given parameters are SI, so meter for the focal_length and radian for the other two """ - focal_length = ct.c_float() - angular_deviation = ct.c_float() - focal_tilt = ct.c_float() - dico={} - self.check(self.dll.ShamrockEepromGetOpticalParams(self.deviceID, - ct.byref(focal_length), - ct.byref(angular_deviation), - ct.byref(focal_tilt))) - dico['focal_length'] = focal_length.value - dico['angular_deviation'] = angular_deviation.value*np.pi/180 - dico['focal_tilt'] = focal_tilt.value*np.pi/180 - - return dico + focal_length, angular_deviation, focal_tilt = ct.c_float(), ct.c_float(), ct.c_float() + self._check(self.dll.ShamrockEepromGetOpticalParams(self.device_id, ct.byref(focal_length), + ct.byref(angular_deviation), ct.byref(focal_tilt))) + return {'focal_length': focal_length.value, + 'angular_deviation': angular_deviation.value*np.pi/180, + 'focal_tilt': focal_tilt.value*np.pi/180} ############################################################################## # Gratings functions ############################################################################## -# All functions in this section have been tested (03/04/2020) -# Comments have to be homogenized -# SI check is OK -# parameters validity is secured -############################################################################## + def get_grating_index(self): + """ Returns the current grating index - def get_grating_number(self): - """ - Returns the current grating identification (0 to self.get_number_gratings-1) - - Tested : yes - SI check : na + @return (int): Current grating index """ grating = ct.c_int() - self.check(self.dll.ShamrockGetGrating(self.deviceID, ct.byref(grating))) - return grating.value-1 + self._check(self.dll.ShamrockGetGrating(self.device_id, ct.byref(grating))) + return grating.value-1 # DLL starts at 1 - def set_grating_number(self, grating): - """ - Sets the required grating (0 to self.get_number_gratings-1) + def set_grating_index(self, value): + """ Sets the grating by index - @param int grating: grating identification number - @return: void - - Tested : yes - SI check : na + @param (int) value: grating index """ - if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.warning('grating number is not in the validity range') - return + self._check(self.dll.ShamrockSetGrating(self.device_id, value+1)) # DLL starts at 1 - if isinstance (grating, int): - self.check(self.dll.ShamrockSetGrating(self.deviceID, grating+1)) - else: - self.log.warning('set_grating function "grating" parameter needs to be int type') - - def get_number_gratings(self): - """ - Returns the number of gratings in the spectrometer - - @return int number_of_gratings - - Tested : yes - SI check : na + def _get_number_gratings(self): + """ Returns the number of gratings in the spectrometer + @return (int): The number of gratings """ number_of_gratings = ct.c_int() - self.check(self.dll.ShamrockGetNumberGratings(self.deviceID, ct.byref(number_of_gratings))) + self._check(self.dll.ShamrockGetNumberGratings(self.device_id, ct.byref(number_of_gratings))) return number_of_gratings.value - def get_grating_info(self, grating): - """ - Returns grating informations - - @param int grating: grating id - @return dictionnary { 'ruling': float (line/m), - 'blaze wavelength': string (nm) - 'home': int (steps) - 'offset': int (steps)} + def _get_grating_info(self, grating): + """ Returns the information on a grating + @param (int) grating: grating index + @return (dict): A dictionary containing keys : 'ruling', 'blaze', 'home' and 'offset' - Tested : yes - SI check : yes + All parameters are in SI + 'ruling' : The number of line per meter (l/m) + 'blaze' : The wavelength for which the grating is blazed + 'home' : #todo + 'offset' : #todo """ line = ct.c_float() blaze = ct.create_string_buffer(32) - home = ct.c_int() - offset = ct.c_int() - dico = {} + home, offset = ct.c_int(), ct.c_int() - if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.warning('grating number is not in the validity range') - return + self._check(self.dll.ShamrockGetGratingInfo(self.device_id, grating+1, + ct.byref(line), ct.byref(blaze), ct.byref(home), ct.byref(offset))) + return {'ruling': line.value * 1e3, # DLL use l/mm + 'blaze': blaze.value, #todo: check unit directly in nm ? + 'home': home.value, + 'offset': offset.value} - if isinstance (grating, int): - self.check(self.dll.ShamrockGetGratingInfo(self.deviceID, grating+1, - ct.byref(line), - ct.byref(blaze), - ct.byref(home), - ct.byref(offset))) - dico['ruling'] = line.value*1E3 - dico['blaze'] = blaze.value - dico['home'] = home.value - dico['offset'] = offset.value - return dico - else: - self.log.warning('set_grating_info function "grating" parameter needs to be int type') - - def get_grating_offset(self, grating): - """ - Returns the grating offset (unit is motor steps) - - @param int grating (between 0 and number_of_gratings) - @return int grating offset (step) - - Tested : yes - SI check : na + def _get_grating_offset(self, grating): + """ Returns the grating offset (in motor steps) - """ - if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.warning('grating number is not in the validity range') - return + @param (int) grating: grating index - if isinstance (grating, int): - grating_offset = ct.c_int() - self.check(self.dll.ShamrockGetGratingOffset(self.deviceID, grating+1, ct.byref(grating_offset))) - return grating_offset.value - else: - self.log.warning('get_grating_offset function "grating" parameter needs to be int type') - - def set_grating_offset(self, grating, offset): + @return (int): grating offset (step) """ - Sets the grating offset (unit is motor step) - @param int grating : grating id (0..self.get_number_gratings() - int offset : grating offset (step) + grating_offset = ct.c_int() + self._check(self.dll.ShamrockGetGratingOffset(self.device_id, grating+1, ct.byref(grating_offset))) + return grating_offset.value - Tested : yes - SI check : na + def _set_grating_offset(self, grating, value): + """ Sets the grating offset (in motor step) + @param (int) grating : grating index + @param (int) value: The offset to set """ - if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.warning('grating number is not in the validity range') - return - - if isinstance (grating, int): - if isinstance(offset, int): - self.check(self.dll.ShamrockSetGratingOffset(self.deviceID, grating+1, offset)) - else: - self.log.warning('set_grating_offset function "offset" parameter needs to be int type') - else: - self.log.warning('set_grating_offset function "grating" parameter needs to be int type') + self._check(self.dll.ShamrockSetGratingOffset(self.device_id, grating+1, value)) ############################################################################## # Wavelength functions -############################################################################## -# All functions in this section have been tested (03/04/2020) -# Comments have to be homogenized -# SI check is OK -# parameters validity is secured ############################################################################## def get_wavelength(self): - """ - Returns the central current wavelength (m) - @return float wavelength (m) + """ Returns the current central wavelength in meter - Tested : yes - SI check : yes + @return (float): current central wavelength (meter) """ wavelength = ct.c_float() - self.check(self.dll.ShamrockGetWavelength(self.deviceID, ct.byref(wavelength))) - return wavelength.value*1E-9 + self._check(self.dll.ShamrockGetWavelength(self.device_id, ct.byref(wavelength))) + return wavelength.value * 1e-9 - def set_wavelength(self, wavelength): - """ - Sets the new central wavelength - @params float wavelength (m) - - Tested : yes - SI check : yes + def set_wavelength(self, value): + """ Sets the new central wavelength in meter + @params (float) value: The new central wavelength (meter) """ + maxi = self.get_constraints().gratings[self._device_id].wavelength_max + if 0 <= value <= maxi: + self.dll.ShamrockSetWavelength.argtypes = [ct.c_int32, ct.c_float] + self._check(self.dll.ShamrockSetWavelength(self.device_id, value * 1e9)) + else: + self.log.error('The wavelength {} is not in the range {}, {}'.format(value*1e9, 0, maxi*1e9)) - minwl, maxwl = self.get_wavelength_limit(self.get_grating_number()) - - if not (minwl <= wavelength <= maxwl): - self.log.warning('the wavelength you ask ({0} nm) is not in the range ' - 'of the current grating ( [{1} ; {2}] nm)'.format(wavelength*1E9, minwl*1E9, maxwl*1E9)) - return - - self.dll.ShamrockSetWavelength.argtypes = [ct.c_int32, ct.c_float] - self.check(self.dll.ShamrockSetWavelength(self.deviceID, wavelength*1e9)) - #self._wl = self.get_calibration(self.get_number_of_pixels()) + def _get_wavelength_limit(self, grating): + """ Returns the wavelength limits of a given grating - return + @params (int) grating: grating index - def get_wavelength_limit(self, grating): + @return tuple(float, float): The minimum and maximum central wavelength permitted by the grating """ - Returns the wavelength limits (m) of the grating (0-self.get_number_gratings) - @params int grating + wavelength_min, wavelength_max = ct.c_float(), ct.c_float() - Tested : yes - SI check : yes + self._check(self.dll.ShamrockGetWavelengthLimits(self.device_id, grating+1, + ct.byref(wavelength_min), ct.byref(wavelength_max))) + return wavelength_min.value*1e-9, wavelength_max.value*1e-9 # DLL uses nanometer - """ - wavelength_min = ct.c_float() - wavelength_max = ct.c_float() + def _set_number_of_pixels(self, value): + """ Internal function to sets the number of pixels of the detector - if not (0 <= grating <= (self.get_number_gratings()-1)): - self.log.warning('grating number is not in the validity range') - return 1 + @param (int) value: The number of pixels of the detector - if not isinstance(grating, int): - self.log.warning('get_wavelength_limit function "grating" parameter needs to be int type') - return 2 - self.check(self.dll.ShamrockGetWavelengthLimits(self.deviceID, grating + 1, ct.byref(wavelength_min) - , ct.byref(wavelength_max))) - return wavelength_min.value * 1E-9, wavelength_max.value * 1E-9 - - def set_number_of_pixels(self, number_of_pixels): + Shamrock DLL can give a estimate of the calibration if the required parameters are given. + This feature is not used by Qudi but is useful to check everything is ok. """ - Sets the number of pixels of the detector (to prepare for calibration) - :param number_of_pixels: int - :return: nothing + self._check(self.dll.ShamrockSetNumberPixels(self.device_id, value)) - Tested : yes - SI check : na - """ - if isinstance (number_of_pixels, int): - self.check(self.dll.ShamrockSetNumberPixels(self.deviceID, number_of_pixels)) - else: - self.log.warning('set_number_of_pixels function "number_of_pixels" parameter needs to be int type') - return + def _set_pixel_width(self, value): + """ Internal function to set the pixel width along the dispersion axis - def set_pixel_width(self, width): - """ - Sets the pixel width along the dispersion axis (to prepare for calibration) - :param width: float unit is m - :return: nothing + @param (float) value: The pixel width of the detector - Tested : yes - SI check : yes + Shamrock DLL can give a estimate of the calibration if the required parameters are given. + This feature is not used by Qudi but is useful to check everything is ok. """ - if not (1e-6 <= width <= 100E-6): - self.log.warning('the pixel width you ask ({0} um) is not in a ' - 'reasonable range ( [{1} ; {2}] um)'.format(width*1E6, 1, 100)) - return + if not (1e-6 <= value <= 100e-6): + self.log.warning('The pixel width you ask ({} um) raises a warning.'.format(value*1e6)) self.dll.ShamrockSetPixelWidth.argtypes = [ct.c_int32, ct.c_float] - self.check(self.dll.ShamrockSetPixelWidth(self.deviceID, width*1E6)) - return - - def get_number_of_pixels(self): - """ - Returns the number of pixel that has to be previously set with self.set_number_of_pixels() - :return: int pixel number + self._check(self.dll.ShamrockSetPixelWidth(self.device_id, value*1e6)) - Tested : yes - SI check : na - """ + def _get_number_of_pixels(self): + """ Returns the number of pixel previously set with self._set_number_of_pixels """ pixel_number = ct.c_int() - self.check(self.dll.ShamrockGetNumberPixels(self.deviceID, ct.byref(pixel_number))) + self._check(self.dll.ShamrockGetNumberPixels(self.device_id, ct.byref(pixel_number))) return pixel_number.value - def get_pixel_width(self): - """ - Returns the pixel width along dispersion axis. - Note that pixel width has to be previously set with self.set_pixel_width(width) - :return: int pixel number - - Tested : yes - SI check : yes - - """ - + def _get_pixel_width(self): + """ Returns the pixel width previously set with self._set_pixel_width """ pixel_width = ct.c_float() - self.check(self.dll.ShamrockGetPixelWidth(self.deviceID, ct.byref(pixel_width))) - return pixel_width.value*1E-6 - + self._check(self.dll.ShamrockGetPixelWidth(self.device_id, ct.byref(pixel_width))) + return pixel_width.value*1e-6 ############################################################################## # Calibration functions ############################################################################## - def get_calibration(self): - """ - Returns the wavelength calibration of each pixel (m) - @params int number_pixels + def _get_calibration(self): + """ Returns the wavelength calibration of each pixel - Tested : yes - SI check : yes + Shamrock DLL can give a estimate of the calibration if the required parameters are given. + This feature is not used by Qudi but is useful to check everything is ok. - Important Note : ShamrockSetNumberPixels and ShamrockSetPixelWidth must have been called - otherwise this function will return -1 + Call _set_number_of_pixels and _set_pixel_width before calling this function. """ - number_pixels = self.get_number_of_pixels() + number_pixels = self._get_number_of_pixels() wl_array = np.ones((number_pixels,), dtype=np.float32) self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] - self.check(self.dll.ShamrockGetCalibration(self.deviceID, wl_array.ctypes.data, number_pixels)) - return wl_array*1E-9 + self._check(self.dll.ShamrockGetCalibration(self.device_id, wl_array.ctypes.data, number_pixels)) + return wl_array*1e-9 # DLL uses nanometer - def set_calibration(self, number_of_pixels, pixel_width, tracks_offset): + def _set_detector_offset(self, value): + """ Sets the detector offset in pixels - self.set_number_of_pixels(number_of_pixels) - self.set_pixel_width(pixel_width) - self.set_detector_offset(tracks_offset) + @param (int) value: The offset to set -############################################################################## -# Detector functions -############################################################################## -# All functions in this section have been tested (03/04/2020) -# Comments have to be homogenized -# SI check is OK -# parameters validity is secured -############################################################################## - - def get_detector_offset(self): + Shamrock DLL can give a estimate of the calibration if the required parameters are given. + This feature is not used by Qudi but is useful to check everything is ok. """ - Returns the detector offset in pixels - :return: int offset - - Tested : yes - SI check : yes + self._check(self.dll.ShamrockSetDetectorOffset(self.device_id, int(value))) - """ + def _get_detector_offset(self): + """ Returns the detector offset previously set with self._set_detector_offset """ offset = ct.c_int() - self.check(self.dll.ShamrockGetDetectorOffset(self.deviceID, ct.byref(offset))) + self._check(self.dll.ShamrockGetDetectorOffset(self.device_id, ct.byref(offset))) return offset.value - def set_detector_offset(self, offset): - """ - Sets the detecotor offset in pixels - :param offset : int - :return: nothing - - Tested : yes - SI check : yes - - """ - if isinstance (offset, int): - self.check(self.dll.ShamrockSetDetectorOffset(self.deviceID, offset)) - else : - self.log.warning('set_detector_offset function "offset" parameter needs to be int type') - ############################################################################## # Ports and Slits functions ############################################################################## -#Important note : slits are adressed with an index. -#Index definition can be found here : -#https://pylablib.readthedocs.io/en/latest/_modules/pylablib/aux_libs/devices/AndorShamrock.html -# 1=input_side - 2=input direct - 3=output side - 4=output direct -# IT HAS TO BE TESTED ON SITE because of the lack of Andor official documentation -############################################################################## -# All functions in this section have been tested (03/04/2020) -# Comments have to be homogenized -# SI check is OK -# parameters validity is secured -############################################################################## - - def flipper_mirror_is_present(self, flipper): - """ - Returns 1 if flipper mirror is present, 0 if not + def _flipper_mirror_is_present(self, side): + """ Returns true if flipper mirror is present on the given side - :param flipper: int 1 is for input, 2 is for output - :return: 1 or 0 - - Test - SI check + @param (str) side: 'input' or 'output' + @param (bool): Whether there is a flipper, hence a second input/output on the side """ - + conversion_dict = {'input': INPUT_CODE, 'output': OUTPUT_CODE} + flipper = conversion_dict[side] present = ct.c_int() - if flipper in [1, 2]: - self.check(self.dll.ShamrockFlipperMirrorIsPresent(self.deviceID, flipper, ct.byref(present))) - else: - self.log.warning('flipper_mirror_is_present : flipper parameter should be 1 for input port and 2 for output port') + self._check(self.dll.ShamrockFlipperMirrorIsPresent(self.device_id, flipper, ct.byref(present))) return present.value def get_input_port(self): - """ - Returns the current port for the input flipper mirror. - 0 is for front port, 1 is for side port - in case of no flipper mirror, front port (0) is used - - Tested : yes - SI check : na + """ Returns the current input port + @return (PortType): current port side """ input_port = ct.c_int() - if self.flipper_mirror_is_present(1) == 1: - self.check(self.dll.ShamrockGetFlipperMirror(self.deviceID, 1, ct.byref(input_port))) - return input_port.value - else: - input_port.value=0 - self.log.info('there is no flipper mirror on input port') - return input_port.value + self.dll.ShamrockGetFlipperMirror(self.device_id, INPUT_CODE, ct.byref(input_port)) + return PortType.INPUT_FRONT if input_port.value == FRONT_CODE else PortType.INPUT_SIDE - def get_output_port(self): + def set_input_port(self, value): + """ Set the current input port + + @param (PortType) value: The port side to set """ - Returns the current port for the output flipper mirror. - 0 is for front port, 1 is for side port - in case of no flipper mirror, front port (0) is used + code = FRONT_CODE if value == PortType.INPUT_FRONT else SIDE_CODE + self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, INPUT_CODE, code)) - Tested : yes - SI check : na + def get_output_port(self): + """ Returns the current output port + @return (PortType): current port side """ output_port = ct.c_int() - if self.flipper_mirror_is_present(2)==1: - self.check(self.dll.ShamrockGetFlipperMirror(self.deviceID, 2, ct.byref(output_port))) - return output_port.value - else: - output_port.value=0 - self.log.info('there is no flipper mirror on output port') - return output_port.value - - def set_input_port(self, input_port): - """ - Sets the input port - 0 is for front port, 1 is for side port - - :param input_port: int. has to be in [0, 1] - :return: nothing + self.dll.ShamrockGetFlipperMirror(self.device_id, OUTPUT_CODE, ct.byref(output_port)) + return PortType.OUTPUT_FRONT if output_port.value == FRONT_CODE else PortType.OUTPUT_SIDE - Tested : yes - SI check : yes + def set_output_port(self, value): + """ Set the current output port + @param (PortType) value: The port side to set """ - if self.flipper_mirror_is_present(1)==1: - if input_port in [0,1] : - self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 1, input_port)) - else: - self.log.warning('set_input_port function : input port should be 0 (front) or 1 (side)') - else: - self.log.warning('there is no flipper mirror on input port') + code = FRONT_CODE if value == PortType.OUTPUT_FRONT else SIDE_CODE + self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, OUTPUT_CODE, code)) - def set_output_port(self, output_port): - """ - Sets the input port - 0 is for front port, 1 is for side port + def _get_slit_index(self, port_type): + """ Returns the slit DLL index of the given port - :param input_port: int. has to be in [0, 1] - :return: nothing - - Tested : yes - SI check : yes + @param (PortType) port_type: The port to inquire + @return (int): slit index as defined by Andor shamrock conventions """ - if self.flipper_mirror_is_present(2) == 1: - if output_port in [0, 1]: - self.check(self.dll.ShamrockSetFlipperMirror(self.deviceID, 2, output_port)) - else: - self.log.warning('set_output_port function : output port should be 0 (front) or 1 (side)') - else: - self.log.warning('there is no flipper mirror on output port') - - def slit_index(self, flipper, port): - """ - Returns the slit index whether there is a slit or not - :param flipper: string flipper - within ['input', 'output'] - :param port: int - within[0,1] for front or side port - :return: int slit_index as defined by Andor convention + conversion_dict = {PortType.INPUT_FRONT: 2, + PortType.INPUT_SIDE: 1, + PortType.OUTPUT_FRONT: 4, + PortType.OUTPUT_SIDE: 3} + return conversion_dict[port_type] - Note : just a local function for ease - """ - if flipper=='input': - if port==1: slit_index=1 - elif port==0: slit_index=2 - else: slit_index=0 - elif flipper=='output': - if port==1:slit_index=3 - elif port==0:slit_index=4 - else: slit_index=0 - else: slit_index=0 - - return slit_index - - def get_auto_slit_width(self, flipper, port): - """ - Returns the input slit width (um) in case of a motorized slit, - :param string flipper - within ['input', 'output'] - int port - within[0,1] for front or side port - :return int offset - slit width, unit is meter (SI) + def get_slit_width(self, port_type): + """ Getter for the current slit width in meter on a given port - Tested : yes - SI check : yes + @param (PortType) port_type: The port to inquire + @return (float): input slit width (in meter) """ - - slit_index=self.slit_index(flipper, port) - if slit_index==0: - self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') - return - + index = self._get_slit_index(port_type) slit_width = ct.c_float() + self._check(self.dll.ShamrockGetAutoSlitWidth(self.device_id, index, ct.byref(slit_width))) + return slit_width.value*1e-6 - if self.auto_slit_is_present(flipper, port) == 1: - self.check(self.dll.ShamrockGetAutoSlitWidth(self.deviceID, slit_index, ct.byref(slit_width))) - return slit_width.value*1E-6 - else: - self.log.warning('there is no slit on this {} port !'.format(flipper)) + def set_slit_width(self, port_type, value): + """ Setter for the input slit width in meter - def set_auto_slit_width(self, flipper, port, slit_width): + @param (PortType) port_type: The port to set + @param (float) value: input slit width (in meter) """ - Sets the new slit width for the required slit - :param flipper: string flipper - within ['input', 'output'] - :param port: int - within[0,1] for front or side port - :param slit_width: float - unit is meter (SI) - :return: nothing - - Tested : yes - SI check : yes - """ - slit_index = self.slit_index(flipper, port) - if slit_index == 0: - self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') - return - self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] - if self.auto_slit_is_present(flipper, port): - if (self.SLIT_MIN_WIDTH <= slit_width <=self.SLIT_MAX_WIDTH): - self.check(self.dll.ShamrockSetAutoSlitWidth(self.deviceID, slit_index, slit_width*1E6)) - else: - self.log.warning('slit_width should be in range [{0}, {1}] m.'.format(self.SLIT_MIN_WIDTH, self.SLIT_MAX_WIDTH)) - else: - self.log.warning('there is no slit on this port') - return + if self.SLIT_MIN_WIDTH <= value <= self.SLIT_MAX_WIDTH: - def get_input_slit_width(self): - """Getter method returning the input slit width of the current used input port. - This method is a simplification of the auto_slit_width function apply for the current configuration - """ - return self.get_auto_slit_width('input', self.get_input_port()) + index = self._get_slit_index(port_type) + self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] + self._check(self.dll.ShamrockSetAutoSlitWidth(self.device_id, index, value*1e6)) + else: + self.log.error('Slit with ({} um) out of range.'.format(value*1e6)) - def set_input_slit_width(self, slit_width): - """Setter method setting the input slit width of the current used input port. - This method is a simplification of the auto_slit_width function apply for the current configuration - """ - return self.set_auto_slit_width('input', self.get_input_port(), slit_width) + def _auto_slit_is_present(self, flipper, port): + """ Return whether the given motorized slit is present or not - def get_output_slit_width(self): - """Getter method returning the output slit width of the current used output port. - This method is a simplification of the auto_slit_width function apply for the current configuration - """ - return self.get_auto_slit_width('output', self.get_output_port()) - - def set_output_slit_width(self, slit_width): - """Setter method setting the output slit width of the current used output port. - This method is a simplification of the auto_slit_width function apply for the current configuration - """ - return self.set_auto_slit_width('output', self.get_output_port(), slit_width) + @param (str) flipper: 'input' or 'output' + @param (str) port: 'front' or 'side' - def auto_slit_is_present(self, flipper, port): + @return (bool): True if a motorized slit is present """ - Return whether the required slit is present or not - :param flipper: string flipper - within ['input', 'output'] - :param port: int - within[0,1] for front or side port - :return: 1 if present, 0 if not - - Tested : yes - SI check : yes - """ - slit_index = self.slit_index(flipper, port) - if slit_index == 0: - self.log.warning('slit parameters are not valid. parameter should be within ([input, output],[0,1]') - return + conversion_dict = {('input', 'front'): 2, # todo: Check this, it does not match the rest of code. If there is a discrepency, we should mention it + ('input', 'side'): 1, + ('output', 'front'): 4, + ('output', 'side'): 3} + slit_index = conversion_dict[(flipper, port)] present = ct.c_int() - self.check(self.dll.ShamrockAutoSlitIsPresent(self.deviceID, slit_index, ct.byref(present))) + self._check(self.dll.ShamrockAutoSlitIsPresent(self.device_id, slit_index, ct.byref(present))) return present.value - ############################################################################## # Shamrock wrapper ############################################################################## -# sdk basic functions + def _shamrock_grating_is_present(self): + """ Wrapper for ShamrockGratingIsPresent DLL function - def shamrock_initialize(self): - self.check(self.dll.ShamrockInitialize()) - return + @returns (bool): True if grating is present - def shamrock_close(self): - self.check(self.dll.ShamrockClose()) - return - - def goto_zero_order(self): - """ - Strange function. No documentation from Andor. Seems to be equivalent to self.set_wavelength(0) ? - """ - self.check(self.dll.ShamrockGotoZeroOrder(self.deviceID)) - return - - def at_zero_order(self): - pass - - def shamrock_grating_is_present(self): - """ - Finds if grating is present - - @returns int 1 if present 0 if not - - Tested : yes + #todo: what does this function mean ??? """ present = ct.c_int() - self.check(self.dll.ShamrockGratingIsPresent(self.deviceID, ct.byref(present))) + self._check(self.dll.ShamrockGratingIsPresent(self.device_id, ct.byref(present))) return present.value - - ############################################################################## - # Shutter mode function (optional) - ############################################################################## - # Shutter mode function are used in logic only if the spectrometer constraints - # dictionary has 'shutter_modes' key filled. If empty this functions will not - # be used and can be ignored. - - def get_shutter_status(self): - """Getter method returning the shutter mode. - - @return: (str) shutter mode (must be compared to the list) - """ - pass - - def set_shutter_status(self, shutter_mode): - """Setter method setting the shutter mode. - - @param shutter_mode: (str) shutter mode (must be compared to the list) - @return: nothing - """ - pass - - - From 7b619e2adb5c381870068d7200f9c8fbbfeea3af Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Wed, 6 May 2020 14:35:41 +0200 Subject: [PATCH 43/49] re-organized shamrock module --- hardware/spectrometer/shamrock.py | 448 +++++++++++++++--------------- 1 file changed, 226 insertions(+), 222 deletions(-) diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index 0d708bdffd..ff3cf877c7 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -48,7 +48,7 @@ SIDE_CODE = 1 -class Shamrock(Base,SpectrometerInterface): +class Shamrock(Base, SpectrometerInterface): """ Hardware module that interface a Shamrock spectrometer from Andor Tested with : @@ -68,9 +68,9 @@ def __init__(self): self._shutter_status = None self._device_id = None -############################################################################## -# Basic functions -############################################################################## + ############################################################################## + # Basic functions + ############################################################################## def on_activate(self): """ Activate module """ try: @@ -101,25 +101,6 @@ def on_deactivate(self): """ De-initialisation performed during deactivation of the module. """ return self._dll.ShamrockClose() - def _get_connected_devices(self): - """ Return a list of serial numbers of the connected devices - - @result (list(str)): A list of the serial numbers as string """ - result = [] - for i in range(self._get_number_devices()): - result.append(self._get_device_serial_number(i)) - return result - - def _get_device_serial_number(self, index): - """ Return the serial number of a hardware by the index number - - @param (int) index: The index the hardware - - @result (str): The serial number as a string - """ - # todo: This function - return 'Please fix me !' - def _build_constraints(self): """ Internal method that build the constraints once at initialisation @@ -166,13 +147,9 @@ def _build_constraints(self): return constraints - def get_constraints(self): - """ Returns all the fixed parameters of the hardware which can be used by the logic. - - @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware - """ - return self._constraints - + ############################################################################## + # DLL useful functions + ############################################################################## def _check(self, status_code): """ Check routine for the received error codes. @@ -183,6 +160,116 @@ def _check(self, status_code): self.log.error('Error in Shamrock with error code {}: {}'.format(status_code, ERROR_CODE[status_code])) return status_code + ############################################################################## + # Interface functions + ############################################################################## + def get_constraints(self): + """ Returns all the fixed parameters of the hardware which can be used by the logic. + + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware + """ + return self._constraints + + def get_grating_index(self): + """ Returns the current grating index + + @return (int): Current grating index + """ + grating = ct.c_int() + self._check(self.dll.ShamrockGetGrating(self.device_id, ct.byref(grating))) + return grating.value-1 # DLL starts at 1 + + def set_grating_index(self, value): + """ Sets the grating by index + + @param (int) value: grating index + """ + self._check(self.dll.ShamrockSetGrating(self.device_id, value+1)) # DLL starts at 1 + + def get_wavelength(self): + """ Returns the current central wavelength in meter + + @return (float): current central wavelength (meter) + """ + wavelength = ct.c_float() + self._check(self.dll.ShamrockGetWavelength(self.device_id, ct.byref(wavelength))) + return wavelength.value * 1e-9 + + def set_wavelength(self, value): + """ Sets the new central wavelength in meter + + @params (float) value: The new central wavelength (meter) + """ + maxi = self.get_constraints().gratings[self._device_id].wavelength_max + if 0 <= value <= maxi: + self.dll.ShamrockSetWavelength.argtypes = [ct.c_int32, ct.c_float] + self._check(self.dll.ShamrockSetWavelength(self.device_id, value * 1e9)) + else: + self.log.error('The wavelength {} is not in the range {}, {}'.format(value*1e9, 0, maxi*1e9)) + + def get_input_port(self): + """ Returns the current input port + + @return (PortType): current port side + """ + input_port = ct.c_int() + self.dll.ShamrockGetFlipperMirror(self.device_id, INPUT_CODE, ct.byref(input_port)) + return PortType.INPUT_FRONT if input_port.value == FRONT_CODE else PortType.INPUT_SIDE + + def set_input_port(self, value): + """ Set the current input port + + @param (PortType) value: The port side to set + """ + code = FRONT_CODE if value == PortType.INPUT_FRONT else SIDE_CODE + self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, INPUT_CODE, code)) + + def get_output_port(self): + """ Returns the current output port + + @return (PortType): current port side + """ + output_port = ct.c_int() + self.dll.ShamrockGetFlipperMirror(self.device_id, OUTPUT_CODE, ct.byref(output_port)) + return PortType.OUTPUT_FRONT if output_port.value == FRONT_CODE else PortType.OUTPUT_SIDE + + def set_output_port(self, value): + """ Set the current output port + + @param (PortType) value: The port side to set + """ + code = FRONT_CODE if value == PortType.OUTPUT_FRONT else SIDE_CODE + self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, OUTPUT_CODE, code)) + + def get_slit_width(self, port_type): + """ Getter for the current slit width in meter on a given port + + @param (PortType) port_type: The port to inquire + + @return (float): input slit width (in meter) + """ + index = self._get_slit_index(port_type) + slit_width = ct.c_float() + self._check(self.dll.ShamrockGetAutoSlitWidth(self.device_id, index, ct.byref(slit_width))) + return slit_width.value*1e-6 + + def set_slit_width(self, port_type, value): + """ Setter for the input slit width in meter + + @param (PortType) port_type: The port to set + @param (float) value: input slit width (in meter) + """ + if self.SLIT_MIN_WIDTH <= value <= self.SLIT_MAX_WIDTH: + + index = self._get_slit_index(port_type) + self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] + self._check(self.dll.ShamrockSetAutoSlitWidth(self.device_id, index, value*1e6)) + else: + self.log.error('Slit with ({} um) out of range.'.format(value*1e6)) + + ############################################################################## + # DLL tools functions + ############################################################################## def _get_number_devices(self): """ Returns the number of devices @@ -192,6 +279,41 @@ def _get_number_devices(self): self._check(self.dll.ShamrockGetNumberDevices(ct.byref(number_of_devices))) return number_of_devices.value + def _get_connected_devices(self): + """ Return a list of serial numbers of the connected devices + + @result (list(str)): A list of the serial numbers as string """ + result = [] + for i in range(self._get_number_devices()): + result.append(self._get_device_serial_number(i)) + return result + + def _get_device_serial_number(self, index): + """ Return the serial number of a hardware by the index number + + @param (int) index: The index the hardware + + @result (str): The serial number as a string + """ + # todo: This function + return 'Please fix me !' + + def _get_slit_index(self, port_type): + """ Returns the slit DLL index of the given port + + @param (PortType) port_type: The port to inquire + + @return (int): slit index as defined by Andor shamrock conventions + """ + conversion_dict = {PortType.INPUT_FRONT: 2, + PortType.INPUT_SIDE: 1, + PortType.OUTPUT_FRONT: 4, + PortType.OUTPUT_SIDE: 3} + return conversion_dict[port_type] + + ############################################################################## + # DLL wrappers used by the interface functions + ############################################################################## def _get_optical_parameters(self): """ Returns the spectrometer optical parameters @@ -206,25 +328,6 @@ def _get_optical_parameters(self): 'angular_deviation': angular_deviation.value*np.pi/180, 'focal_tilt': focal_tilt.value*np.pi/180} -############################################################################## -# Gratings functions -############################################################################## - def get_grating_index(self): - """ Returns the current grating index - - @return (int): Current grating index - """ - grating = ct.c_int() - self._check(self.dll.ShamrockGetGrating(self.device_id, ct.byref(grating))) - return grating.value-1 # DLL starts at 1 - - def set_grating_index(self, value): - """ Sets the grating by index - - @param (int) value: grating index - """ - self._check(self.dll.ShamrockSetGrating(self.device_id, value+1)) # DLL starts at 1 - def _get_number_gratings(self): """ Returns the number of gratings in the spectrometer @@ -254,54 +357,10 @@ def _get_grating_info(self, grating): self._check(self.dll.ShamrockGetGratingInfo(self.device_id, grating+1, ct.byref(line), ct.byref(blaze), ct.byref(home), ct.byref(offset))) return {'ruling': line.value * 1e3, # DLL use l/mm - 'blaze': blaze.value, #todo: check unit directly in nm ? + 'blaze': blaze.value, # todo: check unit directly in nm ? 'home': home.value, 'offset': offset.value} - def _get_grating_offset(self, grating): - """ Returns the grating offset (in motor steps) - - @param (int) grating: grating index - - @return (int): grating offset (step) - """ - grating_offset = ct.c_int() - self._check(self.dll.ShamrockGetGratingOffset(self.device_id, grating+1, ct.byref(grating_offset))) - return grating_offset.value - - def _set_grating_offset(self, grating, value): - """ Sets the grating offset (in motor step) - - @param (int) grating : grating index - @param (int) value: The offset to set - """ - self._check(self.dll.ShamrockSetGratingOffset(self.device_id, grating+1, value)) - -############################################################################## -# Wavelength functions -############################################################################## - - def get_wavelength(self): - """ Returns the current central wavelength in meter - - @return (float): current central wavelength (meter) - """ - wavelength = ct.c_float() - self._check(self.dll.ShamrockGetWavelength(self.device_id, ct.byref(wavelength))) - return wavelength.value * 1e-9 - - def set_wavelength(self, value): - """ Sets the new central wavelength in meter - - @params (float) value: The new central wavelength (meter) - """ - maxi = self.get_constraints().gratings[self._device_id].wavelength_max - if 0 <= value <= maxi: - self.dll.ShamrockSetWavelength.argtypes = [ct.c_int32, ct.c_float] - self._check(self.dll.ShamrockSetWavelength(self.device_id, value * 1e9)) - else: - self.log.error('The wavelength {} is not in the range {}, {}'.format(value*1e9, 0, maxi*1e9)) - def _get_wavelength_limit(self, grating): """ Returns the wavelength limits of a given grating @@ -315,6 +374,41 @@ def _get_wavelength_limit(self, grating): ct.byref(wavelength_min), ct.byref(wavelength_max))) return wavelength_min.value*1e-9, wavelength_max.value*1e-9 # DLL uses nanometer + def _flipper_mirror_is_present(self, flipper): + """ Returns true if flipper mirror is present on the given side + + @param (str) flipper: 'input' or 'output' + + @param (bool): Whether there is a flipper, hence a second input/output on the side + """ + conversion_dict = {'input': INPUT_CODE, 'output': OUTPUT_CODE} + code = conversion_dict[flipper] + present = ct.c_int() + self._check(self.dll.ShamrockFlipperMirrorIsPresent(self.device_id, code, ct.byref(present))) + return present.value + + def _auto_slit_is_present(self, flipper, port): + """ Return whether the given motorized slit is present or not + + @param (str) flipper: 'input' or 'output' + @param (str) port: 'front' or 'side' + + @return (bool): True if a motorized slit is present + """ + conversion_dict = {('input', 'front'): 2, # todo: Check this, it does not match the rest of code. If there is a discrepency, we should mention it + ('input', 'side'): 1, + ('output', 'front'): 4, + ('output', 'side'): 3} + slit_index = conversion_dict[(flipper, port)] + present = ct.c_int() + self._check(self.dll.ShamrockAutoSlitIsPresent(self.device_id, slit_index, ct.byref(present))) + return present.value + + ############################################################################## + # DLL wrapper for calibration functions + # + # This methods can be used to check the calibration of the logic + ############################################################################## def _set_number_of_pixels(self, value): """ Internal function to sets the number of pixels of the detector @@ -325,6 +419,12 @@ def _set_number_of_pixels(self, value): """ self._check(self.dll.ShamrockSetNumberPixels(self.device_id, value)) + def _get_number_of_pixels(self): + """ Returns the number of pixel previously set with self._set_number_of_pixels """ + pixel_number = ct.c_int() + self._check(self.dll.ShamrockGetNumberPixels(self.device_id, ct.byref(pixel_number))) + return pixel_number.value + def _set_pixel_width(self, value): """ Internal function to set the pixel width along the dispersion axis @@ -339,34 +439,11 @@ def _set_pixel_width(self, value): self.dll.ShamrockSetPixelWidth.argtypes = [ct.c_int32, ct.c_float] self._check(self.dll.ShamrockSetPixelWidth(self.device_id, value*1e6)) - def _get_number_of_pixels(self): - """ Returns the number of pixel previously set with self._set_number_of_pixels """ - pixel_number = ct.c_int() - self._check(self.dll.ShamrockGetNumberPixels(self.device_id, ct.byref(pixel_number))) - return pixel_number.value - def _get_pixel_width(self): """ Returns the pixel width previously set with self._set_pixel_width """ pixel_width = ct.c_float() self._check(self.dll.ShamrockGetPixelWidth(self.device_id, ct.byref(pixel_width))) return pixel_width.value*1e-6 -############################################################################## -# Calibration functions -############################################################################## - - def _get_calibration(self): - """ Returns the wavelength calibration of each pixel - - Shamrock DLL can give a estimate of the calibration if the required parameters are given. - This feature is not used by Qudi but is useful to check everything is ok. - - Call _set_number_of_pixels and _set_pixel_width before calling this function. - """ - number_pixels = self._get_number_of_pixels() - wl_array = np.ones((number_pixels,), dtype=np.float32) - self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] - self._check(self.dll.ShamrockGetCalibration(self.device_id, wl_array.ctypes.data, number_pixels)) - return wl_array*1e-9 # DLL uses nanometer def _set_detector_offset(self, value): """ Sets the detector offset in pixels @@ -384,115 +461,23 @@ def _get_detector_offset(self): self._check(self.dll.ShamrockGetDetectorOffset(self.device_id, ct.byref(offset))) return offset.value -############################################################################## -# Ports and Slits functions -############################################################################## - def _flipper_mirror_is_present(self, side): - """ Returns true if flipper mirror is present on the given side - - @param (str) side: 'input' or 'output' - - @param (bool): Whether there is a flipper, hence a second input/output on the side - """ - conversion_dict = {'input': INPUT_CODE, 'output': OUTPUT_CODE} - flipper = conversion_dict[side] - present = ct.c_int() - self._check(self.dll.ShamrockFlipperMirrorIsPresent(self.device_id, flipper, ct.byref(present))) - return present.value - - def get_input_port(self): - """ Returns the current input port - - @return (PortType): current port side - """ - input_port = ct.c_int() - self.dll.ShamrockGetFlipperMirror(self.device_id, INPUT_CODE, ct.byref(input_port)) - return PortType.INPUT_FRONT if input_port.value == FRONT_CODE else PortType.INPUT_SIDE - - def set_input_port(self, value): - """ Set the current input port - - @param (PortType) value: The port side to set - """ - code = FRONT_CODE if value == PortType.INPUT_FRONT else SIDE_CODE - self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, INPUT_CODE, code)) - - def get_output_port(self): - """ Returns the current output port - - @return (PortType): current port side - """ - output_port = ct.c_int() - self.dll.ShamrockGetFlipperMirror(self.device_id, OUTPUT_CODE, ct.byref(output_port)) - return PortType.OUTPUT_FRONT if output_port.value == FRONT_CODE else PortType.OUTPUT_SIDE - - def set_output_port(self, value): - """ Set the current output port - - @param (PortType) value: The port side to set - """ - code = FRONT_CODE if value == PortType.OUTPUT_FRONT else SIDE_CODE - self._check(self.dll.ShamrockSetFlipperMirror(self.device_id, OUTPUT_CODE, code)) - - def _get_slit_index(self, port_type): - """ Returns the slit DLL index of the given port - - @param (PortType) port_type: The port to inquire - - @return (int): slit index as defined by Andor shamrock conventions - """ - conversion_dict = {PortType.INPUT_FRONT: 2, - PortType.INPUT_SIDE: 1, - PortType.OUTPUT_FRONT: 4, - PortType.OUTPUT_SIDE: 3} - return conversion_dict[port_type] - - def get_slit_width(self, port_type): - """ Getter for the current slit width in meter on a given port - - @param (PortType) port_type: The port to inquire - - @return (float): input slit width (in meter) - """ - index = self._get_slit_index(port_type) - slit_width = ct.c_float() - self._check(self.dll.ShamrockGetAutoSlitWidth(self.device_id, index, ct.byref(slit_width))) - return slit_width.value*1e-6 - - def set_slit_width(self, port_type, value): - """ Setter for the input slit width in meter - - @param (PortType) port_type: The port to set - @param (float) value: input slit width (in meter) - """ - if self.SLIT_MIN_WIDTH <= value <= self.SLIT_MAX_WIDTH: - - index = self._get_slit_index(port_type) - self.dll.ShamrockSetAutoSlitWidth.argtypes = [ct.c_int32, ct.c_int32, ct.c_float] - self._check(self.dll.ShamrockSetAutoSlitWidth(self.device_id, index, value*1e6)) - else: - self.log.error('Slit with ({} um) out of range.'.format(value*1e6)) - - def _auto_slit_is_present(self, flipper, port): - """ Return whether the given motorized slit is present or not + def _get_calibration(self): + """ Returns the wavelength calibration of each pixel - @param (str) flipper: 'input' or 'output' - @param (str) port: 'front' or 'side' + Shamrock DLL can give a estimate of the calibration if the required parameters are given. + This feature is not used by Qudi but is useful to check everything is ok. - @return (bool): True if a motorized slit is present + Call _set_number_of_pixels and _set_pixel_width before calling this function. """ - conversion_dict = {('input', 'front'): 2, # todo: Check this, it does not match the rest of code. If there is a discrepency, we should mention it - ('input', 'side'): 1, - ('output', 'front'): 4, - ('output', 'side'): 3} - slit_index = conversion_dict[(flipper, port)] - present = ct.c_int() - self._check(self.dll.ShamrockAutoSlitIsPresent(self.device_id, slit_index, ct.byref(present))) - return present.value + number_pixels = self._get_number_of_pixels() + wl_array = np.ones((number_pixels,), dtype=np.float32) + self.dll.ShamrockGetCalibration.argtypes = [ct.c_int32, ct.c_void_p, ct.c_int32] + self._check(self.dll.ShamrockGetCalibration(self.device_id, wl_array.ctypes.data, number_pixels)) + return wl_array*1e-9 # DLL uses nanometer -############################################################################## -# Shamrock wrapper -############################################################################## + ############################################################################## + # DLL wrapper unused by this module + ############################################################################## def _shamrock_grating_is_present(self): """ Wrapper for ShamrockGratingIsPresent DLL function @@ -503,3 +488,22 @@ def _shamrock_grating_is_present(self): present = ct.c_int() self._check(self.dll.ShamrockGratingIsPresent(self.device_id, ct.byref(present))) return present.value + + def _get_grating_offset(self, grating): + """ Returns the grating offset (in motor steps) + + @param (int) grating: grating index + + @return (int): grating offset (step) + """ + grating_offset = ct.c_int() + self._check(self.dll.ShamrockGetGratingOffset(self.device_id, grating+1, ct.byref(grating_offset))) + return grating_offset.value + + def _set_grating_offset(self, grating, value): + """ Sets the grating offset (in motor step) + + @param (int) grating : grating index + @param (int) value: The offset to set + """ + self._check(self.dll.ShamrockSetGratingOffset(self.device_id, grating+1, value)) From 5a079f31d7124d619736109516bef4dedd8e23f0 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 7 May 2020 17:49:07 +0200 Subject: [PATCH 44/49] added temperature constraints in camera interface --- hardware/spectroscopy_camera_dummy.py | 3 ++- interface/spectroscopy_camera_interface.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/hardware/spectroscopy_camera_dummy.py b/hardware/spectroscopy_camera_dummy.py index 15bb12ac1a..e7e404d8e2 100644 --- a/hardware/spectroscopy_camera_dummy.py +++ b/hardware/spectroscopy_camera_dummy.py @@ -77,10 +77,11 @@ def _build_constraints(self): constraints.pixel_size_width, constraints.pixel_size_width = 13e-6, 13e-6 constraints.internal_gains = [1, 2, 4] constraints.readout_speeds = [50000, 1000000, 3000000] - constraints.has_cooler = True constraints.trigger_modes = ['Internal', 'Dummy TTL'] constraints.has_shutter = True constraints.read_modes = [ReadMode.FVB, ReadMode.MULTIPLE_TRACKS, ReadMode.IMAGE, ReadMode.IMAGE_ADVANCED] + constraints.has_cooler = True + constraints.temperature.min = 0.1 # Very good cooling ! self._constraints = constraints ############################################################################## diff --git a/interface/spectroscopy_camera_interface.py b/interface/spectroscopy_camera_interface.py index ebaa178954..d9d4782571 100644 --- a/interface/spectroscopy_camera_interface.py +++ b/interface/spectroscopy_camera_interface.py @@ -24,6 +24,7 @@ from core.interface import abstract_interface_method from core.meta import InterfaceMetaclass +from core.interface import ScalarConstraint class ReadMode(Enum): @@ -61,9 +62,10 @@ def __init__(self): self.read_modes = [], # Read mode supported by the camera (see ReadMode class) self.internal_gains = [], # Internal gains supported by the camera (list of float) self.readout_speeds = [], # Readout speed supported by the camera, in Hz (list of float) - self.has_cooler = False, # Tells if the camera has a cooling system self.has_shutter = False, # Tells if the camera has a shutter self.trigger_modes = [], # User defined trigger mode (list of string) + self.has_cooler = False, # Tells if the camera has a cooling system + self.temperature = ScalarConstraint(unit='K') # Temperature limits in kelvin class ImageAdvancedParameters: From afdb16738f1546447bc95cd73f9affaa069853c0 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Thu, 7 May 2020 17:49:29 +0200 Subject: [PATCH 45/49] Finished v2 of newton, time for testing ! --- hardware/camera/andor/Newton_940.py | 156 +++++++++++++++++++--------- 1 file changed, 108 insertions(+), 48 deletions(-) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor/Newton_940.py index 4a0f734e40..5b912ba722 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor/Newton_940.py @@ -113,16 +113,21 @@ class Main(Base, SpectroscopyCameraInterface): _dll_location = ConfigOption('dll_location', missing='error') _start_cooler_on_activate = ConfigOption('start_cooler_on_activate', True) - _default_temperature = ConfigOption('default_temperature', 260) + _default_temperature_degree = ConfigOption('default_temperature', -90) # Temperature in °C (not Kelvin !) _default_trigger_mode = ConfigOption('default_trigger_mode', 'INTERNAL') - _shutter_TTL = ConfigOption('shutter_TTL', 1) # todo: explain what this is for the user - _shutter_switching_time = ConfigOption('shutter_switching_time', 100e-3) # todo: explain what this is for the user - _min_temperature = -85 # todo: why ? In this module internally, we can work with degree celsius, as andor users will be used to this. Still, this look rather arbitrary - _max_temperature = -10 # todo: why ? same + # The typ parameter allows the user to control the TTL signal output to an external shutter. + # 0 Output TTL low signal to open shutter + # 1 Output TTL high signal to open shutter + _shutter_TTL = ConfigOption('shutter_TTL', 1) + + # The opening and closing time specify the time required to open and close the shutter + # (this information is required for calculating acquisition timings) + _shutter_switching_time = ConfigOption('shutter_switching_time', 100e-3) # Declarations of attributes to make Pycharm happy - def __init__(self): + def __init__(self, **kwargs): + super().__init__(**kwargs) self._constraints = None self._dll = None self._active_tracks = None @@ -143,7 +148,7 @@ def on_activate(self): self._dll = ct.cdll.LoadLibrary(self._dll_location) except OSError: self.log.error('Error during dll loading of the Andor camera, check the dll path.') - # todo: camera selection by SN ? + # This module handle only one camera. DLL support up to 8 cameras. status_code = self._dll.Initialize() if status_code != OK_CODE: self.log.error('Problem during camera initialization') @@ -154,9 +159,9 @@ def on_activate(self): if self._constraints.has_cooler and self._start_cooler_on_activate: self.set_cooler_on(True) - self.set_read_mode(ReadMode.FVB) # todo: what if not ? + self.set_read_mode(ReadMode.FVB) # Good default value, the logic will handle it from here self.set_trigger_mode(self._default_trigger_mode) - self.set_temperature_setpoint(self._default_temperature) + self.set_temperature_setpoint(self._default_temperature_degree + 273.15) self._set_acquisition_mode(AcquisitionMode.SINGLE_SCAN) self._active_tracks = [] @@ -199,14 +204,17 @@ def _build_constraints(self): constraints.name = self._get_name() constraints.width, constraints.width = self._get_image_size() constraints.pixel_size_width, constraints.pixel_size_width = self._get_pixel_size() - constraints.internal_gains = [1, 2, 4] # # todo : from hardware - constraints.readout_speeds = [50000, 1000000, 3000000] # todo : read from hardware - constraints.has_cooler = True # todo : from hardware ? - constraints.trigger_modes = list(TriggerMode.__members__) # todo : from hardware if only some are available ? - constraints.has_shutter = True # todo : from hardware ? + constraints.internal_gains = self._get_available_gains() + constraints.readout_speeds = self._get_available_speeds() + constraints.trigger_modes = self._get_available_trigger_modes() + constraints.has_shutter = self._has_shutter() constraints.read_modes = [ReadMode.FVB] if constraints.height > 1: constraints.read_modes.extend([ReadMode.MULTIPLE_TRACKS, ReadMode.IMAGE, ReadMode.IMAGE_ADVANCED]) + constraints.has_cooler = True # All Andor camera have one + constraints.temperature.min, constraints.temperature.max = self._get_temperature_range() + constraints.temperature.step = 1 # Andor cameras use integer for control + return constraints def get_constraints(self): @@ -231,7 +239,7 @@ def abort_acquisition(self): """ Aborts the acquisition """ self._check(self._dll.AbortAcquisition()) - def get_ready_state(self): # todo: test this function, i've guessed the dll behavior... + def get_ready_state(self): """ Get the status of the camera, to know if the acquisition is finished or still ongoing. @return (bool): True if the camera is ready, False if an acquisition is ongoing @@ -247,7 +255,7 @@ def get_ready_state(self): # todo: test this function, i've guessed the dll beh else: self._check(code.value) - def get_acquired_data(self): # todo: test for every mode + def get_acquired_data(self): """ Return an array of last acquired data. @return: Data in the format depending on the read mode. @@ -324,7 +332,7 @@ def get_readout_speed(self): @return (float): the readout_speed (Horizontal shift) in Hz """ - return self._readout_speed # todo: not in dll ? + return self._readout_speed # No getter in the DLL def set_readout_speed(self, value): """ Set the readout speed (in Hz) @@ -342,10 +350,8 @@ def get_active_tracks(self): """ Getter method returning the read mode tracks parameters of the camera. @return (list): active tracks positions [(start_1, end_1), (start_2, end_2), ... ] - - This getter is not available in the dll, so its state is handled by this module # todo: confirm ? """ - return self._active_tracks + return self._active_tracks # No getter in the DLL def set_active_tracks(self, value): """ Setter method for the active tracks of the camera. @@ -374,7 +380,7 @@ def get_image_advanced_parameters(self): Should only be used while in IMAGE_ADVANCED mode """ - return self._advanced_image_parameters + return self._advanced_image_parameters # No getter in the DLL def set_image_advanced_parameters(self, value): """ Setter method setting the read mode image parameters of the camera. @@ -410,7 +416,7 @@ def _get_acquisition_mode(self): @return (str): acquisition mode """ - return self._acquisition_mode + return self._acquisition_mode # No getter in the DLL def _set_acquisition_mode(self, value): """ Setter method setting the acquisition mode used by the camera. @@ -461,7 +467,7 @@ def get_gain(self): @return (float): exposure gain """ - return self._preamp_gain # todo: read from hardware ? + return self._preamp_gain # No getter in the DLL def set_gain(self, value): """ Set the gain @@ -482,7 +488,7 @@ def get_trigger_mode(self): @return (str): current trigger mode """ - return self._trigger_mode # todo: read from hardware ? + return self._trigger_mode # No getter in the DLL def set_trigger_mode(self, value): """ Setter method for the trigger mode used by the camera. @@ -507,7 +513,7 @@ def get_shutter_state(self): """ if not self.get_constraints().has_shutter: self.log.error('Can not get state of the shutter, camera does not have a shutter') - return self._shutter_status # todo from hardware + return self._shutter_status # No getter in the DLL def set_shutter_state(self, value): """ Setter method setting the shutter state. @@ -533,7 +539,7 @@ def get_cooler_on(self): @return (bool): True if the cooler is on """ - return self._cooler_status # todo: from harware + return self._cooler_status # No getter in the DLL def set_cooler_on(self, value): """ Setter method for the the cooler status @@ -546,17 +552,15 @@ def set_cooler_on(self, value): status_code = self._dll.CoolerOFF() self._check(status_code) if status_code == OK_CODE: - self._cooler_status = value # todo: no need if handled by hardware + self._cooler_status = value def get_temperature(self): """ Getter method returning the temperature of the camera. @return (float): temperature (in Kelvin) - - The dll uses integers in celsius, so the result will always end with .15, too bad. """ - temp = ct.c_int32() - self._dll.GetTemperature(ct.byref(temp)) + temperature = ct.c_float() + self._dll.GetTemperatureF(ct.byref(temperature)) return temp.value + 273.15 def get_temperature_setpoint(self): @@ -564,20 +568,21 @@ def get_temperature_setpoint(self): @return (float): Current setpoint in Kelvin """ - return self._temperature_setpoint #todo: not in dll ? + return self._temperature_setpoint # Not present in Andor DLL def set_temperature_setpoint(self, value): """ Setter method for the the temperature setpoint of the camera. @param (float) value: New setpoint in Kelvin """ - temperature = int(round(value + 273.15)) - if not(self._min_temperature < temperature < self._max_temperature): - self.log.error('Temperature {}°C is not in the validity range.') + constraints = self.get_constraints().temperature + if not(constraints.min < value < constraints.max): + self.log.error('Temperature {} K is not in the validity range.'.format(value)) return + temperature = int(round(value + 273.15)) status_code = self._check(self._dll.SetTemperature(temperature)) if status_code == OK_CODE: - self._temperature_setpoint = temperature + self._temperature_setpoint = temperature + 273.15 ############################################################################## # Internal functions, for constraints preparation @@ -618,25 +623,80 @@ def _get_pixel_size(self): self._check(self._dll.GetPixelSize(ct.byref(x_px), ct.byref(y_px))) return y_px.value * 1e-6, x_px.value * 1e-6 + def _get_temperature_range(self): + """ Get the temperature minimum and maximum of the camera, in K + + @return tuple(float, float): The minimum minimum and maximum allowed for the setpoint in K """ + mini = ct.c_int() + maxi = ct.c_int() + self._check(self._dll.GetPixelSize(ct.byref(mini), ct.byref(maxi))) + return mini.value+273.15, maxi.value+273.15 + + def _get_available_gains(self): + """ Return a list of the possible preamplifier gains + + @return (list(float)): A list of the gains supported by the camera + """ + number = ct.c_int() + self._dll.GetNumberPreAmpGains(ct.byref(number)) + gains = [] + for i in range(number): + gain = ct.c_float() + self._check(self._dll.GetPreAmpGain(i, ct.byref(gain))) + gains.append(gain.value) + return gains + + def _get_available_speeds(self): + """ Return a list of the possible readout speeds + + @return (list(float)): A list of the readout speeds supported by the camera + """ + number = ct.c_int() + self._dll.GetNumberHSSpeeds(0, ct.byref(number)) # Amplification: 0 = electron multiplication, 1 = conventional + speeds = [] + for i in range(number): + speed = ct.c_float() + self._check(self._dll.GetHSSpeed(0, 0, i, ct.byref(gain))) # AD Channel index, Amplification + speeds.append(speed.value * 1e6) # DLL talks in MHz + return speeds + + def _get_available_trigger_modes(self): + """ Return a list of the trigger mode available to the camera + + @return list(str): A list of the trigger mode available to the dll """ + modes = [] + for mode in TriggerMode: + status_code = self._dll.IsTriggerModeAvailable(mode.value) # The answer is encoded in the status code + if status_code == OK_CODE: + modes.append(mode.name) + return modes + + def _has_shutter(self): + """ Return if the camera have a mechanical shutter installed + + @return (bool): True if the camera have a shutter + """ + result = ct.c_int() + self._check(self._dll.IsInternalMechanicalShutter(ct.byref(result))) + return bool(result.value) # 0: Mechanical shutter not installed, 1: Mechanical shutter installed. + def _get_current_config(self): """ Internal helper method to get the camera parameters in a printable dict. @return (dict): dictionary with camera current configuration. """ - config = { #todo use getters for most of them + config = { 'camera ID..................................': self._get_name(), 'sensor size (pixels).......................': self._get_image_size(), 'pixel size (m)............................': self._get_pixel_size(), - 'acquisition mode...........................': self._acquisition_mode, - 'read mode..................................': self._read_mode, - 'readout speed (Hz).........................': self._readout_speed, - 'gain (x)...................................': self._preamp_gain, - 'trigger_mode...............................': self._trigger_mode, - 'exposure_time..............................': self._exposure, - 'ROI geometry (readmode = IMAGE)............': self._ROI, - 'ROI binning (readmode = IMAGE).............': self._binning, - 'tracks definition (readmode = RANDOM TRACK)': self._active_tracks, - 'temperature (K)............................': self._temperature, - 'shutter_status.............................': self._shutter_status, + 'acquisition mode...........................': self._get_acquisition_mode(), + 'read mode..................................': self.get_read_mode().name, + 'readout speed (Hz).........................': self.get_readout_speed(), + 'gain (x)...................................': self.get_gain(), + 'trigger_mode...............................': self.get_trigger_mode(), + 'exposure_time..............................': self.get_exposure_time(), + 'tracks definition (readmode = RANDOM TRACK)': self.get_active_tracks(), + 'temperature (K)............................': self.get_temperature(), + 'shutter_status.............................': self.get_shutter_state().name, } return config From 711a1dac8c6a2476db96acd25011696788435901 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 11 May 2020 13:58:01 +0200 Subject: [PATCH 46/49] added the dummy spectro updated --- hardware/spectrometer_complete_dummy.py | 210 ++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 hardware/spectrometer_complete_dummy.py diff --git a/hardware/spectrometer_complete_dummy.py b/hardware/spectrometer_complete_dummy.py new file mode 100644 index 0000000000..7d4ce0cd17 --- /dev/null +++ b/hardware/spectrometer_complete_dummy.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" +This module interface Shamrock spectrometer from Andor. + +Qudi 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. + +Qudi 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 Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +na=not applicable +""" +import numpy as np + +from core.module import Base +from core.configoption import ConfigOption + +from interface.spectrometer_complete_interface import SpectrometerInterface +from interface.spectrometer_complete_interface import Grating, PortType, Port, Constraints + + +class Main(Base, SpectrometerInterface): + """ Hardware module that interface a dummy grating spectrometer + """ + + # Declarations of attributes to make Pycharm happy + def __init__(self): + self._constraints = None + self._dll = None + self._shutter_status = None + self._device_id = None + + ############################################################################## + # Basic functions + ############################################################################## + def on_activate(self): + """ Activate module """ + + self._constraints = self._build_constraints() + + self._grating_index = 0 + self._center_wavelength = 600e-9 + self._input_port = PortType.INPUT_FRONT + self._output_port = PortType.OUTPUT_SIDE + self._slit_width = {PortType.INPUT_FRONT: 100e-6, PortType.INPUT_SIDE: 100e-6, PortType.OUTPUT_FRONT: 100e-6} + + + + def on_deactivate(self): + """ De-initialisation performed during deactivation of the module. """ + pass + + def _build_constraints(self): + """ Internal method that build the constraints once at initialisation + + This makes multiple call to the DLL, so it will be called only once by on_activate + """ + constraints = Constraints() + + constraints.focal_length = 0.5 + constraints.angular_deviation = 0.3*np.pi/180 + constraints.focal_tilt = 0 + + number_of_gratings = 3 + + grating = Grating() + grating.ruling = 150e-3 + grating.blaze = 600e-9 + grating.wavelength_max = 1500e-9 + constraints.gratings.append(grating) + + grating = Grating() + grating.ruling = 300e-3 + grating.blaze = 700e-9 + grating.wavelength_max = 1600e-9 + constraints.gratings.append(grating) + + grating = Grating() + grating.ruling = 600e-3 + grating.blaze = 500e-9 + grating.wavelength_max = 1200e-9 + constraints.gratings.append(grating) + + port = Port(PortType.INPUT_FRONT) + port.is_motorized = True + constraints.ports.append(port) + + port = Port(PortType.INPUT_SIDE) + port.is_motorized = True + constraints.ports.append(port) + + port = Port(PortType.OUTPUT_FRONT) + port.is_motorized = True + constraints.ports.append(port) + + port = Port(PortType.OUTPUT_SIDE) + port.is_motorized = False + constraints.ports.append(port) + + for port in constraints.ports: + port.constraints.min = 10e-6 + port.constraints.max = 1500e-6 + + return constraints + + ############################################################################## + # Interface functions + ############################################################################## + def get_constraints(self): + """ Returns all the fixed parameters of the hardware which can be used by the logic. + + @return (Constraints): An object of class Constraints containing all fixed parameters of the hardware + """ + return self._constraints + + def get_grating_index(self): + """ Returns the current grating index + + @return (int): Current grating index + """ + return self._grating_index + + def set_grating_index(self, value): + """ Sets the grating by index + + @param (int) value: grating index + """ + self._grating_index = value + + def get_wavelength(self): + """ Returns the current central wavelength in meter + + @return (float): current central wavelength (meter) + """ + return self._center_wavelength + + def set_wavelength(self, value): + """ Sets the new central wavelength in meter + + @params (float) value: The new central wavelength (meter) + """ + grating_index = self.get_grating_index() + maxi = self.get_constraints().gratings[grating_index].wavelength_max + if 0 <= value <= maxi: + self._center_wavelength = value + else: + self.log.error('The wavelength {} nm is not in the range {} nm , {} nm'.format(value*1e9, 0, maxi*1e9)) + + def get_input_port(self): + """ Returns the current input port + + @return (PortType): current port side + """ + return self._input_port + + def set_input_port(self, value): + """ Set the current input port + + @param (PortType) value: The port side to set + """ + if isinstance(value, PortType): + self._input_port = value + else: + self.log.error("The value is not a port") + + def get_output_port(self): + """ Returns the current output port + + @return (PortType): current port side + """ + return self._output_port + + def set_output_port(self, value): + """ Set the current output port + + @param (PortType) value: The port side to set + """ + if isinstance(value, PortType): + self._output_port = value + else: + self.log.error("The value is not a port") + + def get_slit_width(self, port_type): + """ Getter for the current slit width in meter on a given port + + @param (PortType) port_type: The port to inquire + + @return (float): input slit width (in meter) + """ + return self._slit_width[port_type] + + def set_slit_width(self, port_type, value): + """ Setter for the input slit width in meter + + @param (PortType) port_type: The port to set + @param (float) value: input slit width (in meter) + """ + for port in self.get_constraints().ports: + if port.type == port_type: + if port.is_motorized and (port.constraints.min <= value <= port.constraints.max): + self._slit_width[port_type] = value \ No newline at end of file From e531d1ca69f7ef96cf2572d5399c1c7ffaa34263 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 11 May 2020 14:36:43 +0200 Subject: [PATCH 47/49] removed setup config file from branch --- config/config_Hirondelle/hirondelle.cfg | 50 ------------------------- 1 file changed, 50 deletions(-) delete mode 100644 config/config_Hirondelle/hirondelle.cfg diff --git a/config/config_Hirondelle/hirondelle.cfg b/config/config_Hirondelle/hirondelle.cfg deleted file mode 100644 index 68fb2da81d..0000000000 --- a/config/config_Hirondelle/hirondelle.cfg +++ /dev/null @@ -1,50 +0,0 @@ -# Config file for a simple spectrometer -# -# -global: - # list of modules to load when starting - startup: ['man', 'tray'] # No idea - - module_server: - address: 'localhost' - port: 12345 - - ## For controlling the appearance of the GUI: ( Not Done yet) - stylesheet: 'qdark.qss' - -hardware: - shamrock: - module.Class: 'spectrometer.shamrock.Shamrock' - dll_location: 'C:\Users\uvspace\Documents\Hirondelle200\DLL\ShamrockCIF.dll' - - newton: - module.Class: 'camera.andor.Newton_940.Newton940' - dll_location: 'C:\Users\uvspace\Documents\Hirondelle200\DLL\atmcd64d.dll' - -logic: - spectrumlogic: - module.Class: 'spectrum_logic.SpectrumLogic' - connect: - spectrometer: 'shamrock' - camera: 'newton' - savelogic: 'savelogic' - - savelogic: - module.Class: 'save_logic.SaveLogic' - win_data_directory: 'C:/Data' # DO NOT CHANGE THE DIRECTORY HERE! ONLY IN THE CUSTOM FILE! - unix_data_directory: 'Data/' - log_into_daily_directory: True - save_pdf: True - save_png: True - -gui: - tray: - module.Class: 'trayicon.TrayIcon' - - man: - module.Class: 'manager.managergui.ManagerGui' - - spectrometer: - module.Class: 'PLspectrum.PL_spectrum_gui.PLspectrumGUI' - connect: - spectrumlogic: 'spectrumlogic' \ No newline at end of file From d405a87fffa7ea2e784778207ece11bf505e4506 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 11 May 2020 14:43:05 +0200 Subject: [PATCH 48/49] renamed interfaces --- .../grating_spectrometer_dummy.py} | 6 +++--- hardware/{ => dummies}/spectroscopy_camera_dummy.py | 0 hardware/spectrometer/shamrock.py | 6 +++--- ...plete_interface.py => grating_spectrometer_interface.py} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename hardware/{spectrometer_complete_dummy.py => dummies/grating_spectrometer_dummy.py} (96%) rename hardware/{ => dummies}/spectroscopy_camera_dummy.py (100%) rename interface/{spectrometer_complete_interface.py => grating_spectrometer_interface.py} (98%) diff --git a/hardware/spectrometer_complete_dummy.py b/hardware/dummies/grating_spectrometer_dummy.py similarity index 96% rename from hardware/spectrometer_complete_dummy.py rename to hardware/dummies/grating_spectrometer_dummy.py index 7d4ce0cd17..ff9bc0eae1 100644 --- a/hardware/spectrometer_complete_dummy.py +++ b/hardware/dummies/grating_spectrometer_dummy.py @@ -24,11 +24,11 @@ from core.module import Base from core.configoption import ConfigOption -from interface.spectrometer_complete_interface import SpectrometerInterface -from interface.spectrometer_complete_interface import Grating, PortType, Port, Constraints +from interface.grating_spectrometer_interface import SpectrometerInterface +from interface.grating_spectrometer_interface import Grating, PortType, Port, Constraints -class Main(Base, SpectrometerInterface): +class Main(Base, GratingSpectrometerInterface): """ Hardware module that interface a dummy grating spectrometer """ diff --git a/hardware/spectroscopy_camera_dummy.py b/hardware/dummies/spectroscopy_camera_dummy.py similarity index 100% rename from hardware/spectroscopy_camera_dummy.py rename to hardware/dummies/spectroscopy_camera_dummy.py diff --git a/hardware/spectrometer/shamrock.py b/hardware/spectrometer/shamrock.py index ff3cf877c7..6293953330 100644 --- a/hardware/spectrometer/shamrock.py +++ b/hardware/spectrometer/shamrock.py @@ -25,8 +25,8 @@ from core.module import Base from core.configoption import ConfigOption -from interface.spectrometer_complete_interface import SpectrometerInterface -from interface.spectrometer_complete_interface import Grating, PortType, Port, Constraints +from interface.grating_spectrometer_interface import SpectrometerInterface +from interface.grating_spectrometer_interface import Grating, PortType, Port, Constraints ERROR_CODE = { 20201: "SHAMROCK_COMMUNICATION_ERROR", @@ -48,7 +48,7 @@ SIDE_CODE = 1 -class Shamrock(Base, SpectrometerInterface): +class Shamrock(Base, GratingSpectrometerInterface): """ Hardware module that interface a Shamrock spectrometer from Andor Tested with : diff --git a/interface/spectrometer_complete_interface.py b/interface/grating_spectrometer_interface.py similarity index 98% rename from interface/spectrometer_complete_interface.py rename to interface/grating_spectrometer_interface.py index 4c6474f694..9ddce9bda1 100644 --- a/interface/spectrometer_complete_interface.py +++ b/interface/grating_spectrometer_interface.py @@ -59,7 +59,7 @@ def __init__(self): self.ports = [] # List of Ports object -class SpectrometerInterface(metaclass=InterfaceMetaclass): +class GratingSpectrometerInterface(metaclass=InterfaceMetaclass): """ This is the interface class to define the controls for spectrometer hardware This interface only deals with the part of the spectrometer that set central wavelength and gratings. From b00b49188346983cb6e78bfb90049315c9d8d613 Mon Sep 17 00:00:00 2001 From: Alrik Durand Date: Mon, 11 May 2020 14:57:02 +0200 Subject: [PATCH 49/49] renamed science camera interface --- hardware/camera/{andor/Newton_940.py => andor_camera.py} | 4 ++-- ...spectroscopy_camera_dummy.py => science_camera_dummy.py} | 6 +++--- ...copy_camera_interface.py => science_camera_interface.py} | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename hardware/camera/{andor/Newton_940.py => andor_camera.py} (99%) rename hardware/dummies/{spectroscopy_camera_dummy.py => science_camera_dummy.py} (98%) rename interface/{spectroscopy_camera_interface.py => science_camera_interface.py} (99%) diff --git a/hardware/camera/andor/Newton_940.py b/hardware/camera/andor_camera.py similarity index 99% rename from hardware/camera/andor/Newton_940.py rename to hardware/camera/andor_camera.py index 5b912ba722..ba05a7342c 100644 --- a/hardware/camera/andor/Newton_940.py +++ b/hardware/camera/andor_camera.py @@ -31,8 +31,8 @@ from core.module import Base from core.configoption import ConfigOption -from interface.spectroscopy_camera_interface import SpectroscopyCameraInterface -from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters, ShutterState +from interface.science_camera_interface import SpectroscopyCameraInterface +from interface.science_camera_interface import ReadMode, Constraints, ImageAdvancedParameters, ShutterState # Bellow are the classes used by Andor dll. They are not par of Qudi interfaces diff --git a/hardware/dummies/spectroscopy_camera_dummy.py b/hardware/dummies/science_camera_dummy.py similarity index 98% rename from hardware/dummies/spectroscopy_camera_dummy.py rename to hardware/dummies/science_camera_dummy.py index e7e404d8e2..5fb2614e4b 100644 --- a/hardware/dummies/spectroscopy_camera_dummy.py +++ b/hardware/dummies/science_camera_dummy.py @@ -21,13 +21,13 @@ import numpy as np from core.module import Base -from interface.spectroscopy_camera_interface import SpectroscopyCameraInterface -from interface.spectroscopy_camera_interface import ReadMode, Constraints, ImageAdvancedParameters +from interface.science_camera_interface import ScienceCameraInterface +from interface.science_camera_interface import ReadMode, Constraints, ImageAdvancedParameters from qtpy import QtCore -class Main(Base, SpectroscopyCameraInterface): +class Main(Base, ScienceCameraInterface): """ This module is the dummy module for the SpectroscopyCameraInterface interface spectroscopy_camera_dummy: diff --git a/interface/spectroscopy_camera_interface.py b/interface/science_camera_interface.py similarity index 99% rename from interface/spectroscopy_camera_interface.py rename to interface/science_camera_interface.py index d9d4782571..d901c1e177 100644 --- a/interface/spectroscopy_camera_interface.py +++ b/interface/science_camera_interface.py @@ -79,7 +79,7 @@ def __init__(self): self.vertical_end = None # Has to be an integer -class SpectroscopyCameraInterface(metaclass=InterfaceMetaclass): +class ScienceCameraInterface(metaclass=InterfaceMetaclass): """ This interface is used to manage a camera used for spectroscopy """ @abstract_interface_method