From 42a867b72a4c0b4b15c8a7b311630a59cdf196a1 Mon Sep 17 00:00:00 2001 From: lange50 Date: Thu, 13 Mar 2025 15:40:24 -0400 Subject: [PATCH] Updates to get everything working for a terascan measurement. I believe it all works as intended now. Next steps are getting the diffraction grating to work correctly. --- cfg/terascan.cfg | 48 +- src/qudi/gui/terascan/terascan_gui.py | 72 +- src/qudi/gui/terascan/terascan_main_window.py | 24 +- .../hardware/dummy/data_instream_dummy.py | 588 ------------ src/qudi/hardware/dummy/wavemeter_dummy.py | 60 ++ .../hardware/timetagger/swabian_tagger.py | 2 +- .../hardware/wavemeter/high_finesse_proxy.py | 500 ----------- .../wavemeter/high_finesse_wavemeter.py | 499 ++--------- .../wavemeter/high_finesse_wrapper.py | 664 -------------- ...{high_finesse_constants.py => wlmConst.py} | 400 ++++++--- src/qudi/hardware/wavemeter/wlmData.py | 588 ++++++++++++ src/qudi/interface/data_instream_interface.py | 273 ------ .../interface/simple_wavemeter_interface.py | 17 + src/qudi/logic/common/data_in_stream_logic.py | 834 ------------------ src/qudi/logic/common/fast_counter_logic.py | 3 +- src/qudi/logic/common/wavemeter_logic.py | 84 ++ src/qudi/logic/terascan_logic.py | 27 +- 17 files changed, 1187 insertions(+), 3496 deletions(-) delete mode 100644 src/qudi/hardware/dummy/data_instream_dummy.py create mode 100644 src/qudi/hardware/dummy/wavemeter_dummy.py delete mode 100644 src/qudi/hardware/wavemeter/high_finesse_proxy.py delete mode 100644 src/qudi/hardware/wavemeter/high_finesse_wrapper.py rename src/qudi/hardware/wavemeter/{high_finesse_constants.py => wlmConst.py} (51%) create mode 100644 src/qudi/hardware/wavemeter/wlmData.py delete mode 100644 src/qudi/interface/data_instream_interface.py create mode 100644 src/qudi/interface/simple_wavemeter_interface.py delete mode 100644 src/qudi/logic/common/data_in_stream_logic.py create mode 100644 src/qudi/logic/common/wavemeter_logic.py diff --git a/cfg/terascan.cfg b/cfg/terascan.cfg index ed61d18..a398735 100644 --- a/cfg/terascan.cfg +++ b/cfg/terascan.cfg @@ -43,7 +43,7 @@ logic: module.Class: 'terascan_logic.TerascanLogic' connect: laser: scanning_laser_logic - wavemeter: time_series_reader_logic + wavemeter: wavemeter_logic counter: fast_counter_logic daq: daq_reader_logic # Note that this logic assumes there is exactly one (digital) input to the DAQ. options: @@ -53,19 +53,15 @@ logic: daq_reader_logic: module.Class: 'common.daq_reader_logic.DAQReaderLogic' connect: - daq: nidaq #daq_reader_dummy + daq: nidaq # daq_reader_dummy options: update_interval: 0 # Period in ms to check for data updates. Integers only. 0 is as fast as possible - time_series_reader_logic: - module.Class: 'common.data_in_stream_logic.DataInStreamLogic' - options: - max_frame_rate: 20 # optional (default: 20Hz) - channel_buffer_size: 1048576 # optional (default: 1MSample) - max_raw_data_bytes: 1073741824 # optional (default: 1GB) + wavemeter_logic: + module.Class: 'common.wavemeter_logic.WavemeterLogic' connect: - streamer: wavemeter # instream_dummy + wavemeter: wavemeter # wavemeter_dummy fast_counter_logic: @@ -96,23 +92,9 @@ hardware: type: 0 # 0 for Digital, 1 for Analog name: 'line0' # The name as idintified by the card port: 1 # port number identified by the card - - wavemeter_proxy: - module.Class: 'wavemeter.high_finesse_proxy.HighFinesseProxy' - options: - watchdog_interval: 1.0 # how often the watchdog checks for errors/changes in s - dllPath: 'C:\Program Files (x86)\HighFinesse\Wavelength Meter WS6 3167\Projects\64\wlmData.dll' wavemeter: module.Class: 'wavemeter.high_finesse_wavemeter.HighFinesseWavemeter' - connect: - proxy: wavemeter_proxy - options: - channels: - laser: - switch_ch: 1 # channel on the wavemeter switch - unit: 'm' # wavelength (m) or frequency (Hz) - exposure: 10 # exposure time in ms, optional swabian_timetagger: module.Class: 'timetagger.swabian_tagger.SwabianTimeTagger' @@ -133,27 +115,11 @@ hardware: daq_reader_dummy: module.Class: 'dummy.daq_reader_dummy.DAQReaderDummy' - instream_dummy: - module.Class: 'dummy.data_instream_dummy.InStreamDummy' - options: - channel_names: - - 'digital 1' - - 'analog 1' - - 'digital 2' - channel_units: - - 'Hz' - - 'V' - - 'Hz' - channel_signals: # Can be 'counts' or 'sine' - - 'counts' - - 'sine' - - 'counts' - data_type: 'float64' - sample_timing: 'CONSTANT' # Can be 'CONSTANT', 'TIMESTAMP' or 'RANDOM' - fast_counter_dummy: module.Class: 'dummy.fast_counter_dummy.FastCounterDummy' + wavemeter_dummy: + module.Class: 'dummy.wavemeter_dummy.WavemeterDummy' scanning_laser_dummy: module.Class: 'dummy.scanning_laser_dummy.ScanningLaserDummy' diff --git a/src/qudi/gui/terascan/terascan_gui.py b/src/qudi/gui/terascan/terascan_gui.py index 2391139..ca152bb 100644 --- a/src/qudi/gui/terascan/terascan_gui.py +++ b/src/qudi/gui/terascan/terascan_gui.py @@ -3,7 +3,8 @@ __all__ = ['TerascanGui'] import numpy as np -from PySide2 import QtCore +import os +from PySide2 import QtCore, QtGui from typing import List from qudi.util.datastorage import TextDataStorage @@ -11,6 +12,7 @@ from qudi.core.connector import Connector from qudi.core.statusvariable import StatusVar from qudi.gui.terascan.terascan_main_window import TerascanMainWindow +from qudi.util.paths import get_artwork_dir from qudi.logic.terascan_logic import TerascanData @@ -61,6 +63,10 @@ def on_activate(self) -> None: self._terascan_logic().sigScanFinished.connect( self._scan_finished, QtCore.Qt.QueuedConnection ) + + self._terascan_logic().sigLaserLocked.connect( + self._laser_lock_ui, QtCore.Qt.QueuedConnection + ) # Outputs: @@ -76,6 +82,11 @@ def on_activate(self) -> None: ) self._data = [] + + # Turn on update timer: + self.__timer = QtCore.QTimer() + self.__timer.setSingleShot(False) + self.__timer.timeout.connect(self._update_plot) # Show the main window and raise it above all others self.show() @@ -85,6 +96,7 @@ def on_deactivate(self) -> None: self._terascan_logic().sigWavelengthUpdated.disconnect(self._wavelength_changed) self._terascan_logic().sigCountsUpdated.disconnect(self._receive_data) self._terascan_logic().sigScanFinished.disconnect(self._scan_finished) + self._terascan_logic().sigLaserLocked.disconnect(self._laser_lock_ui) # Use "plain" disconnects (without argument) only on signals owned by this module self._mw.start_wavelength.valueChanged.disconnect() @@ -94,8 +106,14 @@ def on_deactivate(self) -> None: self.sigStopMeasurement.disconnect() self.sigSetWavelengths.disconnect() + # disable update timer: + self.__timer.stop() + self.__timer.timeout.disconnect() + self.__timer = None + # Close main window self._mw.close() + def show(self) -> None: """ Mandatory method to show the main window """ @@ -107,6 +125,7 @@ def _start_changed(self, wave: float) -> None: """ Qt slot to be called upon wavelength change """ self._start_wavelength = wave self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength) + @QtCore.Slot(float) def _stop_changed(self, wave: float) -> None: @@ -119,13 +138,26 @@ def _stop_changed(self, wave: float) -> None: def _start_stop_pressed(self) -> None: """ Qt slot to be called upon wavelength change """ if self._mw.start_stop_button.text() == 'Start Measurement': + self._update_ui(True) + self.__timer.start(250) + self.sigStartMeasurement.emit() + else: + self._update_ui(False) + self.__timer.stop() + self.sigStopMeasurement.emit() + + + + def _update_ui(self, running: bool) -> None: + if running: self._mw.start_stop_button.setText('Stop Measurement') self._mw.plot_widget.setXRange(self._start_wavelength, self._stop_wavelength) - print('start measurement in terascan_gui') - self.sigStartMeasurement.emit() + self._mw._statusbar.clearMessage() + self._mw._progress_bar.setValue(0) + else: self._mw.start_stop_button.setText('Start Measurement') - self.sigStopMeasurement.emit() + self._mw._statusbar.showMessage('Ready') @QtCore.Slot() def _scan_finished(self) -> None: @@ -133,20 +165,14 @@ def _scan_finished(self) -> None: @QtCore.Slot(object) def _receive_data(self, data: List[TerascanData]) -> None: - img = np.zeros((len(data),2)) - for i in range(len(data) - 1): - # print(np.average(data[i].counts)) - img[i][0] = data[i].wavelength - img[i][1] = data[i].counts - # img[i][0] = 775 + 15 * np.random.rand() - # img[i][1] = 100 * np.random.rand() - - self._mw.data_item.setData(x = img[:, 0], y = img[:, 1]) + self._data = data @QtCore.Slot(float) def _wavelength_changed(self, wave: float) -> None: self._current_wavelength = wave + percent = 100 * (wave*1e-3 - self._start_wavelength) / (self._stop_wavelength - self._start_wavelength); + self._mw._progress_bar.setValue(int(round(percent))) @QtCore.Slot() def _save_data(self) -> None: @@ -156,3 +182,23 @@ def _save_data(self) -> None: ) ds.save_data(self._data) + + @QtCore.Slot(bool) + def _laser_lock_ui(self, locked: bool) -> None: + icon = 'network-connect' if locked else 'network-disconnect' + pix = QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', icon)) + self._mw._locked_indicator.setPixmap(pix.scaled(16, 16)) + + + + def _update_plot(self) -> None: + if (len(self._data) == 0): + return + + img = np.zeros((len(self._data),2)) + for i in range(len(self._data) - 1): + img[i][0] = self._data[i].wavelength*1e-3 + img[i][1] = self._data[i].counts + + self._mw.data_item.setData(x = img[:, 0], y = img[:, 1]) + diff --git a/src/qudi/gui/terascan/terascan_main_window.py b/src/qudi/gui/terascan/terascan_main_window.py index 6bd9453..8dbe594 100644 --- a/src/qudi/gui/terascan/terascan_main_window.py +++ b/src/qudi/gui/terascan/terascan_main_window.py @@ -35,16 +35,20 @@ def __init__(self, *args, **kwargs): menu.addAction(self.action_close) self.setMenuBar(menu_bar) - # Create toolbar - # toolbar = QtWidgets.QToolBar() - # toolbar.setAllowedAreas(QtCore.Qt.AllToolBarAreas) - # self.action_start_video = QtWidgets.QAction('Start Video') - # self.action_start_video.setCheckable(True) - # toolbar.addAction(self.action_start_video) - # self.action_capture_frame = QtWidgets.QAction('Capture Frame') - # self.action_capture_frame.setCheckable(True) - # toolbar.addAction(self.action_capture_frame) - # self.addToolBar(QtCore.Qt.TopToolBarArea, toolbar) + # Create statusbar and indicators + self._statusbar = self.statusBar() + self._statusbar.showMessage('Ready') + + self._progress_bar = QtWidgets.QProgressBar() + self._progress_bar.setRange(0, 100) + self._progress_bar.setValue(0) + + + self._locked_indicator = QtWidgets.QLabel() + self._locked_indicator.setPixmap(QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', 'network-disconnect')).scaled(16, 16)) + + self._statusbar.addWidget(self._locked_indicator) + self._statusbar.addWidget(self._progress_bar) # Initialize widgets self.start_stop_button = QtWidgets.QPushButton('Start Measurement') diff --git a/src/qudi/hardware/dummy/data_instream_dummy.py b/src/qudi/hardware/dummy/data_instream_dummy.py deleted file mode 100644 index 9ac59fc..0000000 --- a/src/qudi/hardware/dummy/data_instream_dummy.py +++ /dev/null @@ -1,588 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -This file contains the qudi hardware module to use a National Instruments X-series card as mixed -signal input data streamer. - -Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this -distribution and on - -This file is part of qudi. - -Qudi is free software: you can redistribute it and/or modify it under the terms of -the GNU Lesser 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along with qudi. -If not, see . -""" - -import time -import numpy as np -from enum import Enum -from typing import List, Iterable, Union, Optional, Tuple, Callable, Sequence - -from qudi.core.configoption import ConfigOption -from qudi.util.constraints import ScalarConstraint -from qudi.util.mutex import Mutex -from qudi.util.helpers import is_integer_type -from qudi.interface.data_instream_interface import DataInStreamInterface, DataInStreamConstraints -from qudi.interface.data_instream_interface import StreamingMode, SampleTiming - - -def _make_sine_func(sample_rate: float) -> Callable[[np.ndarray, np.ndarray], None]: - freq = sample_rate / (20 + 80 * np.random.rand()) - amp = 1 + np.random.rand() * 9 - noise_lvl = amp * (0.1 + np.random.rand() * 0.4) - def make_sine(x, y): - # y[:] = np.sin(2 * np.pi * freq * x) - np.sin(2 * np.pi * freq * x, out=y) - y *= amp - noise = np.random.rand(x.size) - noise *= 2 * noise_lvl - noise -= noise_lvl - y += noise - return make_sine - - -def _make_counts_func(sample_rate: float) -> Callable[[np.ndarray, np.ndarray], None]: - count_lvl = 1_00 + np.random.rand() * (5_000 - 1_00) - def make_counts(x, y): - y[:] = count_lvl - y += np.random.poisson(count_lvl, y.size) - return make_counts - - -class SignalShape(Enum): - INVALID = -1 - SINE = 0 - COUNTS = 1 - - -class SampleGenerator: - """ Generator object that periodically generates new samples based on a certain timebase but - the actual sample timestamps can be irregular depending on configured SampleTiming - """ - def __init__(self, - signal_shapes: Iterable[SignalShape], - sample_rate: float, - sample_timing: SampleTiming, - streaming_mode: StreamingMode, - data_type: Union[type, str], - buffer_size: int, - ) -> None: - self.data_type = np.dtype(data_type).type - self.sample_rate = float(sample_rate) - self.sample_timing = SampleTiming(sample_timing) - self.streaming_mode = StreamingMode(streaming_mode) - self.signal_shapes = [SignalShape(shape) for shape in signal_shapes] - self.buffer_size = buffer_size - - self.__start = 0 # buffer start sample index - self.__end = 0 # buffer end sample index - self.__available_samples = 0 - self._sample_buffer = None - self._timestamp_buffer = None - self._generator_functions = list() - self._start_time = self._last_time = 0.0 - self.restart() - - @property - def available_samples(self) -> int: - self.generate_samples() - return self.__available_samples - - @property - def channel_count(self) -> int: - return len(self._generator_functions) - - @property - def _buffer_size(self) -> int: - return self._sample_buffer.size - - @property - def _buffer_sample_size(self) -> int: - return self._buffer_size // self.channel_count - - @property - def _free_samples(self) -> int: - return max(0, self._buffer_sample_size - self.__available_samples) - - def restart(self) -> None: - # Init generator functions - self._generator_functions = list() - for shape in self.signal_shapes: - if shape == SignalShape.SINE: - self._generator_functions.append(_make_sine_func(self.sample_rate)) - elif shape == SignalShape.COUNTS: - self._generator_functions.append(_make_counts_func(self.sample_rate)) - else: - raise ValueError(f'Invalid SignalShape encountered: {shape}') - # Init buffer - self.__start = self.__end = 0 - self.__available_samples = 0 - self._sample_buffer = np.empty(self.buffer_size * self.channel_count, dtype=self.data_type) - if self.sample_timing == SampleTiming.TIMESTAMP: - self._timestamp_buffer = np.zeros(self.buffer_size, dtype=np.float64) - else: - self._timestamp_buffer = None - # Set start time - self._start_time = self._last_time = time.perf_counter() - - def generate_samples(self) -> float: - """ Generates new samples in free buffer space and updates buffer pointers. If new samples - do not fit into buffer, raise an OverflowError. - """ - now = time.perf_counter() - elapsed_time = now - self._last_time - time_offset = self._last_time - self._start_time - samples_per_channel = int(elapsed_time * self.sample_rate) # truncate - if self.streaming_mode == StreamingMode.FINITE: - samples_per_channel = min(samples_per_channel, self._free_samples) - elapsed_time = samples_per_channel / self.sample_rate - ch_count = self.channel_count - - if samples_per_channel > 0: - # Generate x-axis (time) for sample generation - if self.sample_timing == SampleTiming.CONSTANT: - x = np.arange(samples_per_channel, dtype=np.float64) - x /= self.sample_rate - else: - # randomize ticks within time interval for non-regular sampling - x = np.random.rand(samples_per_channel) - x.sort() - x *= elapsed_time - x += time_offset - - # ToDo: - # Generate samples and write into buffer - buffer = self._sample_buffer.reshape([self._buffer_sample_size, ch_count]) - end = self.__end + samples_per_channel - if end > self._buffer_sample_size: - first_samples = self._buffer_sample_size - self.__end - end -= self._buffer_sample_size - for ch_idx, generator in enumerate(self._generator_functions): - generator(x[:first_samples], buffer[self.__end:, ch_idx]) - generator(x[first_samples:], buffer[:end, ch_idx]) - if self.sample_timing == SampleTiming.TIMESTAMP: - self._timestamp_buffer[self.__end:] = x[:first_samples] - self._timestamp_buffer[:end] = x[first_samples:] - else: - for ch_idx, generator in enumerate(self._generator_functions): - generator(x, buffer[self.__end:end, ch_idx]) - if self.sample_timing == SampleTiming.TIMESTAMP: - self._timestamp_buffer[self.__end:end] = x - # Update pointers - self.__available_samples += samples_per_channel - self.__end = end - self._last_time += elapsed_time - if self.__available_samples > self._buffer_sample_size: - raise OverflowError('Sample buffer has overflown. Decrease sample rate or increase ' - 'data readout rate.') - return self._last_time - - def read_samples(self, - sample_buffer: np.ndarray, - samples_per_channel: int, - timestamp_buffer: Optional[np.ndarray] = None) -> int: - ch_count = self.channel_count - buf_size = self._buffer_size - samples = min(self.__available_samples, samples_per_channel) * ch_count - start = self.__start * ch_count - end = start + samples - if end > buf_size: - first_samples = buf_size - start - end -= buf_size - sample_buffer[:first_samples] = self._sample_buffer[start:] - sample_buffer[first_samples:first_samples + end] = self._sample_buffer[:end] - if timestamp_buffer is not None: - timestamp_buffer[:first_samples // ch_count] = self._timestamp_buffer[self.__start:] - timestamp_buffer[first_samples // ch_count:(first_samples + end) // ch_count] = self._timestamp_buffer[:end // ch_count] - else: - sample_buffer[:samples] = self._sample_buffer[start:end] - if timestamp_buffer is not None: - timestamp_buffer[:samples // ch_count] = self._timestamp_buffer[self.__start:end // ch_count] - # Update pointers - samples //= ch_count - self.__start = end // ch_count - self.__available_samples -= samples - return samples - - def wait_get_available_samples(self, samples: int) -> int: - available = self.available_samples - if available < samples: - # Wait for bulk time - time.sleep((samples - available) / self.sample_rate) - available = self.available_samples - # Wait a little more if necessary - while available < samples: - time.sleep(1 / self.sample_rate) - available = self.available_samples - return available - - -class InStreamDummy(DataInStreamInterface): - """ - A dummy module to act as data in-streaming device (continuously read values) - - Example config for copy-paste: - - instream_dummy: - module.Class: 'dummy.data_instream_dummy.InStreamDummy' - options: - channel_names: - - 'digital 1' - - 'analog 1' - - 'digital 2' - channel_units: - - 'Hz' - - 'V' - - 'Hz' - channel_signals: # Can be 'counts' or 'sine' - - 'counts' - - 'sine' - - 'counts' - data_type: 'float64' - sample_timing: 'CONSTANT' # Can be 'CONSTANT', 'TIMESTAMP' or 'RANDOM' - """ - # config options - _channel_names = ConfigOption(name='channel_names', - missing='error', - constructor=lambda names: [str(x) for x in names]) - _channel_units = ConfigOption(name='channel_units', - missing='error', - constructor=lambda units: [str(x) for x in units]) - _channel_signals = ConfigOption( - name='channel_signals', - missing='error', - constructor=lambda signals: [SignalShape[x.upper()] for x in signals] - ) - _data_type = ConfigOption(name='data_type', - default='float64', - missing='info', - constructor=lambda typ: np.dtype(typ).type) - _sample_timing = ConfigOption(name='sample_timing', - default='CONSTANT', - missing='info', - constructor=lambda timing: SampleTiming[timing.upper()]) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self._thread_lock = Mutex() - self._active_channels = list() - self._sample_generator = None - self._constraints = None - - def on_activate(self): - # Sanity check ConfigOptions - if not (len(self._channel_names) == len(self._channel_units) == len(self._channel_signals)): - raise ValueError('ConfigOptions "channel_names", "channel_units" and "channel_signals" ' - 'must contain same number of elements') - if len(set(self._channel_names)) != len(self._channel_names): - raise ValueError('ConfigOptions "channel_names" must not contain duplicates') - if is_integer_type(self._data_type) and any(sig == SignalShape.SINE for sig in self._channel_signals): - self.log.warning('Integer data type not supported for Sine signal shape. ' - 'Falling back to numpy.float64 data type instead.') - self._data_type = np.float64 - - self._constraints = DataInStreamConstraints( - channel_units=dict(zip(self._channel_names, self._channel_units)), - sample_timing=self._sample_timing, - streaming_modes=[StreamingMode.CONTINUOUS, StreamingMode.FINITE], - data_type=self._data_type, - channel_buffer_size=ScalarConstraint(default=1024**2, - bounds=(128, 1024**3), - increment=1, - enforce_int=True), - sample_rate=ScalarConstraint(default=10.0, bounds=(0.1, 1024**2), increment=0.1) - ) - self._active_channels = list(self._constraints.channel_units) - self._sample_generator = SampleGenerator( - signal_shapes=self._channel_signals, - sample_rate=self._constraints.sample_rate.default, - sample_timing=self._constraints.sample_timing, - streaming_mode=self._constraints.streaming_modes[0], - data_type=self._constraints.data_type, - buffer_size=self._constraints.channel_buffer_size.default - ) - - def on_deactivate(self): - # Free memory - self._sample_generator = None - - @property - def constraints(self) -> DataInStreamConstraints: - """ Read-only property returning the constraints on the settings for this data streamer. """ - return self._constraints - - @property - def available_samples(self) -> int: - """ Read-only property to return the currently available number of samples per channel ready - to read from buffer. - """ - with self._thread_lock: - if self.module_state() == 'locked': - return self._sample_generator.available_samples - return 0 - - @property - def sample_rate(self) -> float: - """ Read-only property returning the currently set sample rate in Hz. - For SampleTiming.CONSTANT this is the sample rate of the hardware, for any other timing mode - this property represents only a hint to the actual hardware timebase and can not be - considered accurate. - """ - return self._sample_generator.sample_rate - - @property - def channel_buffer_size(self) -> int: - """ Read-only property returning the currently set buffer size in samples per channel. - The total buffer size in bytes can be estimated by: - * * numpy.nbytes[] - - For StreamingMode.FINITE this will also be the total number of samples to acquire per - channel. - """ - return self._sample_generator.buffer_size - - @property - def streaming_mode(self) -> StreamingMode: - """ Read-only property returning the currently configured StreamingMode Enum """ - return self._sample_generator.streaming_mode - - @property - def active_channels(self) -> List[str]: - """ Read-only property returning the currently configured active channel names """ - return self._active_channels.copy() - - def configure(self, - active_channels: Sequence[str], - streaming_mode: Union[StreamingMode, int], - channel_buffer_size: int, - sample_rate: float) -> None: - """ Configure a data stream. See read-only properties for information on each parameter. """ - with self._thread_lock: - if self.module_state() == 'locked': - raise RuntimeError('Unable to configure data stream while it is already running') - - # Cache current values to restore them if configuration fails - old_channels = self.active_channels - old_streaming_mode = self.streaming_mode - old_buffer_size = self.channel_buffer_size - old_sample_rate = self.sample_rate - try: - self._set_active_channels(active_channels) - self._set_streaming_mode(streaming_mode) - self._set_channel_buffer_size(channel_buffer_size) - self._set_sample_rate(sample_rate) - except Exception as err: - self._set_active_channels(old_channels) - self._set_streaming_mode(old_streaming_mode) - self._set_channel_buffer_size(old_buffer_size) - self._set_sample_rate(old_sample_rate) - raise RuntimeError('Error while trying to configure data in-streamer') from err - - def _set_active_channels(self, channels: Iterable[str]) -> None: - channels = set(channels) - if not channels.issubset(self._constraints.channel_units): - raise ValueError(f'Invalid channels to set active {channels}. Allowed channels are ' - f'{set(self._constraints.channel_units)}') - channel_shapes = {ch: self._channel_signals[idx] for idx, ch in - enumerate(self._constraints.channel_units) if ch in channels} - self._sample_generator.signal_shapes = [shape for shape in channel_shapes.values()] - self._active_channels = list(channel_shapes) - - def _set_streaming_mode(self, mode: Union[StreamingMode, int]) -> None: - try: - mode = StreamingMode(mode.value) - except AttributeError: - mode = StreamingMode(mode) - if (mode == StreamingMode.INVALID) or mode not in self._constraints.streaming_modes: - raise ValueError( - f'Invalid streaming mode to set ({mode}). Allowed StreamingMode values are ' - f'[{", ".join(str(mod) for mod in self._constraints.streaming_modes)}]' - ) - self._sample_generator.streaming_mode = mode - - def _set_channel_buffer_size(self, samples: int) -> None: - self._constraints.channel_buffer_size.check(samples) - self._sample_generator.buffer_size = samples - - def _set_sample_rate(self, rate: Union[int, float]) -> None: - rate = float(rate) - self._constraints.sample_rate.check(rate) - self._sample_generator.sample_rate = rate - - def start_stream(self) -> None: - """ Start the data acquisition/streaming """ - with self._thread_lock: - if self.module_state() == 'idle': - self.module_state.lock() - try: - self._sample_generator.restart() - except: - self.module_state.unlock() - raise - else: - self.log.warning('Unable to start input stream. It is already running.') - - def stop_stream(self) -> None: - """ Stop the data acquisition/streaming """ - with self._thread_lock: - if self.module_state() == 'locked': - self.module_state.unlock() - - def read_data_into_buffer(self, - data_buffer: np.ndarray, - samples_per_channel: int, - timestamp_buffer: Optional[np.ndarray] = None) -> None: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - Samples of all channels are stored interleaved in contiguous memory. - In case of a multidimensional buffer array, this buffer will be flattened before written - into. - The 1D data_buffer can be unraveled into channel and sample indexing with: - - data_buffer.reshape([, ]) - - The data_buffer array must have the same data type as self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array has to be - provided to be filled with timestamps corresponding to the data_buffer array. It must be - able to hold at least items: - - This function is blocking until the required number of samples has been acquired. - """ - with self._thread_lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - if (self.constraints.sample_timing == SampleTiming.TIMESTAMP) and timestamp_buffer is None: - raise RuntimeError('SampleTiming.TIMESTAMP mode requires a timestamp buffer array') - - channel_count = len(self.active_channels) - if data_buffer.size < samples_per_channel * channel_count: - raise RuntimeError( - f'data_buffer too small ({data_buffer.size:d}) to hold all requested ' - f'samples for all channels ({channel_count:d} * {samples_per_channel:d} = ' - f'{samples_per_channel * channel_count:d})' - ) - if (timestamp_buffer is not None) and (timestamp_buffer.size < samples_per_channel): - raise RuntimeError( - f'timestamp_buffer too small ({timestamp_buffer.size:d}) to hold all requested ' - f'samples ({samples_per_channel:d})' - ) - - self._sample_generator.wait_get_available_samples(samples_per_channel) - if timestamp_buffer is None: - self._sample_generator.read_samples( - sample_buffer=data_buffer, - samples_per_channel=samples_per_channel - ) - else: - self._sample_generator.read_samples( - sample_buffer=data_buffer, - samples_per_channel=samples_per_channel, - timestamp_buffer=timestamp_buffer - ) - - def read_available_data_into_buffer(self, - data_buffer: np.ndarray, - timestamp_buffer: Optional[np.ndarray] = None) -> int: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - All samples for each channel are stored in consecutive blocks one after the other. - The number of samples read per channel is returned and can be used to slice out valid data - from the buffer arrays like: - - valid_data = data_buffer[: * ] - valid_timestamps = timestamp_buffer[:] - - See "read_data_into_buffer" documentation for more details. - - This method will read all currently available samples into buffer. If number of available - samples exceeds buffer size, read only as many samples as fit into the buffer. - """ - with self._thread_lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - self._sample_generator.generate_samples() - available_samples = self._sample_generator.available_samples - if self.constraints.sample_timing == SampleTiming.TIMESTAMP: - if timestamp_buffer is None: - raise RuntimeError( - 'SampleTiming.TIMESTAMP mode requires a timestamp buffer array' - ) - timestamp_buffer = timestamp_buffer[:available_samples] - channel_count = len(self.active_channels) - data_buffer = data_buffer[:channel_count * available_samples] - return self._sample_generator.read_samples(sample_buffer=data_buffer, - samples_per_channel=available_samples, - timestamp_buffer=timestamp_buffer) - - def read_data(self, - samples_per_channel: Optional[int] = None - ) -> Tuple[np.ndarray, Union[np.ndarray, None]]: - """ Read data from the stream buffer into a 1D numpy array and return it. - All samples for each channel are stored in consecutive blocks one after the other. - The returned data_buffer can be unraveled into channel samples with: - - data_buffer.reshape([, ]) - - The numpy array data type is the one defined in self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array will be - returned as well with timestamps corresponding to the data_buffer array. - - If samples_per_channel is omitted all currently available samples are read from buffer. - This method will not return until all requested samples have been read or a timeout occurs. - """ - with self._thread_lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - - self._sample_generator.generate_samples() - if samples_per_channel is None: - samples_per_channel = self._sample_generator.available_samples - else: - self._sample_generator.wait_get_available_samples(samples_per_channel) - - data_buffer = np.empty(len(self.active_channels) * samples_per_channel, - dtype=self._constraints.data_type) - if self.constraints.sample_timing == SampleTiming.TIMESTAMP: - timestamp_buffer = np.empty(samples_per_channel, dtype=np.float64) - else: - timestamp_buffer = None - if samples_per_channel > 0: - self._sample_generator.read_samples(sample_buffer=data_buffer, - samples_per_channel=samples_per_channel, - timestamp_buffer=timestamp_buffer) - return data_buffer, timestamp_buffer - - def read_single_point(self): - """ This method will initiate a single sample read on each configured data channel. - In general this sample may not be acquired simultaneous for all channels and timing in - general can not be assured. Us this method if you want to have a non-timing-critical - snapshot of your current data channel input. - May not be available for all devices. - The returned 1D numpy array will contain one sample for each channel. - - @return numpy.ndarray: 1D array containing one sample for each channel. Empty array - indicates error. - """ - with self._thread_lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - - data_buffer = np.empty(len(self.active_channels), dtype=self._constraints.data_type) - if self.constraints.sample_timing == SampleTiming.TIMESTAMP: - timestamp_buffer = np.empty(1, dtype=np.float64) - else: - timestamp_buffer = None - self._sample_generator.wait_get_available_samples(1) - self._sample_generator.read_samples(sample_buffer=np.expand_dims(data_buffer, axis=0), - samples_per_channel=1, - timestamp_buffer=timestamp_buffer) - return data_buffer, timestamp_buffer diff --git a/src/qudi/hardware/dummy/wavemeter_dummy.py b/src/qudi/hardware/dummy/wavemeter_dummy.py new file mode 100644 index 0000000..8be8c68 --- /dev/null +++ b/src/qudi/hardware/dummy/wavemeter_dummy.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +This module reads data from High Finesse wavemeters + +Copyright (c) 2025, the QuPIDC qudi developers. + +Qudi is free software: you can redistribute it and/or modify it under the terms of +the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along with qudi. +If not, see . +""" + +import numpy as np +from PySide2 import QtCore + +from qudi.interface.simple_wavemeter_interface import SimpleWavemeterInterface + +import qudi.hardware.wavemeter.wlmConst as wlmConst +import qudi.hardware.wavemeter.wlmData as wlmData + + +class WavemeterDummy(SimpleWavemeterInterface): + """ + Hardware file for high finesse wavemeter + + Assumes the DLL is installed in the system path so does not need to be manually located + + Example config for copy-paste: + + dummy_wavemeter: + module.Class: 'dummy.dummy_wavemeter.WavemeterDummy' + """ + + sigWavelengthUpdated = QtCore.Signal(np.ndarray) # 1-d array of wavelengths in nm + + def on_activate(self): + """ Activate module. + """ + + self._data = np.zeros((4, 1)) + + + def on_deactivate(self): + """ Deactivate module. + """ + pass + + + def get_wavelengths(self) -> np.ndarray: + """ Get the wavelengths from the wavemeter + """ + self._data = 700 + 50*np.random.rand((4, 1)) + return self._data + diff --git a/src/qudi/hardware/timetagger/swabian_tagger.py b/src/qudi/hardware/timetagger/swabian_tagger.py index 2f32926..b4b9548 100644 --- a/src/qudi/hardware/timetagger/swabian_tagger.py +++ b/src/qudi/hardware/timetagger/swabian_tagger.py @@ -144,7 +144,7 @@ def configure(self, bin_width_s, record_length_s): tagger=self._tagger, channels=[self._channel_config[i] for i in self._channel_config], binwidth=int(np.round(self._bin_width * 1000)), # in ps - n_values=int(self._record_length), + n_values=1, ) self.counter.stop() diff --git a/src/qudi/hardware/wavemeter/high_finesse_proxy.py b/src/qudi/hardware/wavemeter/high_finesse_proxy.py deleted file mode 100644 index fd2ab34..0000000 --- a/src/qudi/hardware/wavemeter/high_finesse_proxy.py +++ /dev/null @@ -1,500 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -This module acts as a proxy for the HighFinesse wavemeter. It directly communicates -with the hardware and can process callbacks from it. Being a module, there should only -ever be a single instance running in one qudi process. - -Copyright (c) 2023, the qudi developers. See the AUTHORS.md file at the top-level directory of this -distribution and on - -This file is part of qudi. - -Qudi is free software: you can redistribute it and/or modify it under the terms of -the GNU Lesser 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along with qudi. -If not, see . -""" -import os -import time -from typing import Optional, List, Set, TYPE_CHECKING, Dict -from ctypes import byref, cast, c_double, c_int, c_char_p, c_long, POINTER, WINFUNCTYPE, WinDLL -from PySide2.QtCore import QObject -from qudi.core.threadmanager import ThreadManager -from qudi.core.logger import get_logger -from qudi.core.module import Base -from qudi.core.configoption import ConfigOption -from qudi.util.mutex import Mutex - -import qudi.hardware.wavemeter.high_finesse_constants as high_finesse_constants -from qudi.hardware.wavemeter.high_finesse_wrapper import load_dll, setup_dll, MIN_VERSION -if TYPE_CHECKING: - from qudi.hardware.wavemeter.high_finesse_wavemeter import HighFinesseWavemeter - - -THREAD_NAME_WATCHDOG = 'wavemeter_callback_error_watchdog' - - -class Watchdog(QObject): - """A watchdog that can take care of errors in the callback function and checks for other changes.""" - def __init__(self, proxy: 'HighFinesseProxy', watch_interval: float): - super().__init__() - self._proxy = proxy - self.log = get_logger(__name__) - self._watch_interval = watch_interval - self._stop = False - - def loop(self) -> None: - while not self._stop: - if self._proxy.error_in_callback: - self.handle_error() - if self._proxy.module_state() == 'locked': - self.check_for_channel_activation_change() - time.sleep(self._watch_interval) - - def stop_loop(self) -> None: - self._stop = True - - def check_for_channel_activation_change(self) -> None: - actual_active_channels = set(self._proxy.get_active_channels()) - if self._proxy.get_connected_channels() != actual_active_channels: - self.log.warning('Channel was deactivated or activated through GUI.') - self._proxy.stop_everything() - - def handle_error(self) -> None: - self.log.warning('Error in callback function.') - self._proxy.stop_everything() - self._proxy.error_in_callback = False - - -class HighFinesseProxy(Base): - """Proxy between a physical HighFinesse wavemeter and one or multiple HighFinesse wavemeter hardware modules. - - Example config for copy-paste: - - wavemeter_proxy: - module.Class: 'wavemeter.high_finesse_proxy.HighFinesseProxy' - options: - watchdog_interval: 1.0 # how often the watchdog checks for errors/changes in s - dllPath: 'C:\Program Files (x86)\HighFinesse\Wavelength Meter WS6 3167\Projects\64\wlmData.dll' - """ - - _watchdog_interval: float = ConfigOption(name='watchdog_interval', default=1.0) - _dllPath: str = ConfigOption( - name='dllPath', - default=r'C:\Program Files (x86)\HighFinesse\Wavelength Meter WS6 3167\Projects\64\wlmData.dll', - missing="warn" - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._lock = Mutex() - - self._wavemeter_dll: Optional[WinDLL] = None - self._watchdog: Optional[Watchdog] = None - self._thread_manager: ThreadManager = ThreadManager.instance() - self._callback_function: Optional[callable] = None - self.error_in_callback: bool = False - self._wm_has_switch: bool = False - - self._connected_instream_modules: Dict['HighFinesseWavemeter', Set[int]] = {} - - def on_activate(self) -> None: - if self._check_for_second_instance(): - raise RuntimeError('There is already a running proxy instance. ' - 'Did you configure more than a single instance of this proxy?') - - # load and prepare the wavemeter DLL - try: - self._wavemeter_dll = load_dll(self._dllPath) - except FileNotFoundError as e: - raise ValueError('There is no wavemeter installed on this computer.\n' - 'Please install a High Finesse wavemeter and try again.') from e - else: - v = [self._wavemeter_dll.GetWLMVersion(i) for i in range(4)] - if v[0] == high_finesse_constants.GetFrequencyError.ErrWlmMissing.value: - raise RuntimeError('The wavemeter application is not active. ' - 'Start the wavemeter application before activating the qudi module.') - - self.log.info(f'Successfully loaded wavemeter DLL of WS{v[0]} {v[1]},' - f' software revision {v[2]}, compilation number {v[3]}.') - - software_rev = v[2] - if software_rev < MIN_VERSION: - self.log.warning(f'The wavemeter DLL software revision {software_rev} is older than the lowest revision ' - f'tested to be working with the wrapper ({MIN_VERSION}). ' - f'Setting up the wavemeter DLL might fail.') - try: - setup_dll(self._wavemeter_dll) - except AttributeError: - self.log.warning('One or more function is not available. The wavemeter version is likely outdated.') - - # try to activate the multi-channel switch and check if switch is present - self._wavemeter_dll.SetSwitcherMode(True) - is_active = self._wavemeter_dll.GetSwitcherMode(0) - if is_active: - self._wm_has_switch = True - else: - self._wm_has_switch = False - - self._set_up_watchdog() - - def on_deactivate(self) -> None: - self._tear_down_watchdog() - del self._wavemeter_dll - - def connect_instreamer(self, module: 'HighFinesseWavemeter', channels: List[int]): - """ - Connect an instreamer module to the proxy. - The proxy will start to put new samples into the instreamer buffer. - """ - if module not in self._connected_instream_modules: - with self._lock: - # do channel activation in a lock to prevent the watchdog from stopping things - not_connected_yet = set(channels) - self.get_connected_channels() - for ch in not_connected_yet: - self._activate_channel(ch) - self._connected_instream_modules[module] = set(channels) - if self._callback_function is None: - self._activate_only_connected_channels() - self._start_measurement() - self._start_callback() - else: - self.log.warning('Instream module is already connected.') - - def disconnect_instreamer(self, module: 'HighFinesseWavemeter'): - """ Disconnect an instreamer module from the proxy. """ - if module in self._connected_instream_modules: - channels_disconnecting_instreamer = self._connected_instream_modules[module] - with self._lock: - del self._connected_instream_modules[module] - if not self._connected_instream_modules: - self._stop_callback() - else: - # deactivate channels that are not connected by other instreamers - for ch in (channels_disconnecting_instreamer - self.get_connected_channels()): - self._deactivate_channel(ch) - else: - self.log.warning('Instream module is not connected and can therefore not be disconnected.') - - def sample_rate(self) -> float: - """ - Estimate the current sample rate by the exposure times per channel and switching times. - :return: sample rate in Hz - """ - exposure_times = [] - active_channels = self.get_active_channels() - for ch in active_channels: - t = self._wavemeter_dll.GetExposureNum(ch, 1, 0) - exposure_times.append(t) - total_exposure_time = sum(exposure_times) - - switching_time = 12 - n_channels = len(active_channels) - turnaround_time_ms = total_exposure_time + n_channels * switching_time - - return 1e3 / turnaround_time_ms - - def set_exposure_time(self, ch: int, exp_time: float) -> None: - """ Set the exposure time for a specific switch channel. """ - err = self._wavemeter_dll.SetExposureNum(ch, 1, exp_time) - if err: - raise RuntimeError(f'Wavemeter error while setting exposure time of channel {ch}: ' - f'{high_finesse_constants.ResultError(err)}') - - def get_active_channels(self) -> List[int]: - """ - Get a list of all active channels on the multi-channel switch. - :return: list of active channels - """ - if not self._wm_has_switch: - return [1] - - active_channels = [] - active = c_long() - err = 0 - ch = 1 - while err != high_finesse_constants.ResultError.ChannelNotAvailable.value: - err = self._wavemeter_dll.GetSwitcherSignalStates(ch, byref(active), byref(c_long())) - if active: - active_channels.append(ch) - ch += 1 - return active_channels - - def get_connected_channels(self) -> Set[int]: - """Channels on the multi-channel switch which are active on a connected instreamer.""" - channels = set() - for i in self._connected_instream_modules.values(): - channels = channels | i - return channels - - def stop_everything(self) -> None: - """Meant to be called from watchdog.""" - self.log.warning('Stopping all streams.') - streamers = list(self._connected_instream_modules).copy() - self._stop_callback() - self._connected_instream_modules = {} - for streamer in streamers: - # stop all streams without them triggering the proxy disconnect - streamer.stop_stream_watchdog() - - # --- PID related methods --- - - def get_pid_setting(self, output_port: int, cmi_val: int): - """ - Generic method to get PID values and settings - @return: PID value or setting - """ - i_val = c_long() - d_val = c_double() - err = self._wavemeter_dll.GetPIDSetting(cmi_val, output_port, byref(i_val), byref(d_val)) - if err == 1: - return d_val.value, i_val.value - else: - raise RuntimeError(f'Error while getting PID value/setting: {high_finesse_constants.ResultError(err)}') - - def set_pid_setting(self, output_port: int, cmi_val: int, d_val: float = 0.0, i_val: int = 0): - """ Generic method to set PID values and settings """ - i_val = c_long(i_val) - d_val = c_double(d_val) - err = self._wavemeter_dll.SetPIDSetting(cmi_val, output_port, i_val, d_val) - if err: - raise RuntimeError(f'Error while setting PID value/setting: {high_finesse_constants.ResultError(err)}') - - def get_setpoint(self, output_port: int) -> float: - """ - Get the setpoint for a specific control voltage output port - @return (float): The setpoint for this output port - """ - pidc = c_char_p(b'0' * 1024) - err = self._wavemeter_dll.GetPIDCourseNum(output_port, pidc) - if err == high_finesse_constants.ResultError.NoErr.value: - # wavemeter returns comma instead of point - val = pidc.value.decode('utf-8').replace(',', '.') - # wavemeter returns '= 123,456' when first turned on - if val.startswith('= '): - val = val[2:] - try: - val = float(val) - except ValueError: - raise ValueError('Could not convert PID course to a number.') - return val - else: - raise RuntimeError(f'Error while getting setpoint: {high_finesse_constants.ResultError(err)}') - - def set_setpoint(self, output_port: int, setpoint: float): - """ Set the setpoint for a specific output port """ - # wavemeter wants comma instead of point - setpoint = str(setpoint).replace('.', ',').encode('utf-8') - # convert setpoint to char array with 1024 bytes - err = self._wavemeter_dll.SetPIDCourseNum(output_port, setpoint) - if err: - raise RuntimeError(f'Error while setting setpoint: {high_finesse_constants.ResultError(err)}') - - def set_manual_value(self, output_port: int, voltage: float) -> None: - """ Set the control value to put out when PID is not running. """ - d_voltage = c_double(1e3 * voltage) # wavemeter wants mV - err = self._wavemeter_dll.SetDeviationSignalNum(output_port, d_voltage) - if err: - raise RuntimeError(f'Error while manual value: {high_finesse_constants.ResultError(err)}') - - def get_pid_enabled(self) -> bool: - """ - Get the PID status - @return (bool): True if PID is enabled, False otherwise - """ - return self._wavemeter_dll.GetDeviationMode(False) - - def set_pid_enabled(self, enabled: bool): - """ Set the PID status """ - err = self._wavemeter_dll.SetDeviationMode(enabled) - if err: - raise RuntimeError(f'Error while setting PID enabled: {high_finesse_constants.ResultError(err)}') - - def get_laser_control_setting(self, output_port: int, cmi_val: int): - """ - Generic method to get laser control settings - @return: laser control setting - """ - pidc = c_char_p(b'0' * 1024) - i_val = c_long() - d_val = c_double() - err = self._wavemeter_dll.GetLaserControlSetting(cmi_val, output_port, byref(i_val), byref(d_val), pidc) - if err == 1: - return d_val.value, i_val.value, pidc.value - else: - raise RuntimeError(f'Error while getting laser control setting: {high_finesse_constants.ResultError(err)}') - - def get_control_value(self, output_port: int): - """ - Get the control value for a specific voltage output port - @return (float): The control value in V for the output port - """ - i_val = c_long(output_port) - d_val = c_double() - return 1e-3 * self._wavemeter_dll.GetDeviationSignalNum(i_val, d_val) - - def get_wavelength(self, channel: int) -> float: - """ - Get the current wavelength for a specific input channel - @return (float): wavelength in m - """ - i_val = c_long(channel) - d_val = c_double() - res = self._wavemeter_dll.GetWavelengthNum(i_val, d_val) - if res in [e.value for e in high_finesse_constants.GetFrequencyError]: - raise RuntimeError(f'Error while getting process value: {high_finesse_constants.ResultError(res)}') - else: - return 1e-9 * res - - # --- protected methods --- - - def _check_for_second_instance(self) -> bool: - """Check if there already is a proxy running.""" - return THREAD_NAME_WATCHDOG in self._thread_manager.thread_names - - def _activate_channel(self, ch: int) -> None: - """ Activate a channel on the multi-channel switch. """ - if not self._wm_has_switch: - if ch == 1: - return - else: - raise RuntimeError(f'Cannot activate channel {ch}: wavemeter does not have a multi-channel switch.') - - err = self._wavemeter_dll.SetSwitcherSignalStates(ch, 1, 1) - if err: - raise RuntimeError( - f'Wavemeter error while activating channel {ch}: {high_finesse_constants.ResultError(err)}' - ) - - def _deactivate_channel(self, ch: int) -> None: - """ Deactivate a channel on the multi-channel switch. """ - if not self._wm_has_switch: - if ch == 1: - return - else: - raise RuntimeError(f'Cannot deactivate channel {ch}: wavemeter does not have a multi-channel switch.') - - err = self._wavemeter_dll.SetSwitcherSignalStates(ch, 0, 0) - if err: - raise RuntimeError(f'Wavemeter error while deactivating channel {ch}: ' - f'{high_finesse_constants.ResultError(err)}') - - def _activate_only_connected_channels(self) -> None: - """Activate all channels active on a connected instreamer and disable all others.""" - connected_channels = self.get_connected_channels() - if not connected_channels: - raise RuntimeError('Cannot deactivate all channels.') - - for ch in connected_channels: - self._activate_channel(ch) - for ch in self.get_active_channels(): - if ch not in connected_channels: - self._deactivate_channel(ch) - - def _start_measurement(self) -> None: - if self._wm_has_switch: - self._wavemeter_dll.SetSwitcherMode(True) - err = self._wavemeter_dll.Operation(high_finesse_constants.cCtrlStartMeasurement) - if err: - raise RuntimeError(f'Wavemeter error while starting measurement: {high_finesse_constants.ResultError(err)}') - - def _set_up_watchdog(self) -> None: - self._watchdog_thread = self._thread_manager.get_new_thread(THREAD_NAME_WATCHDOG) - self._watchdog = Watchdog(self, self._watchdog_interval) - self._watchdog.moveToThread(self._watchdog_thread) - self._watchdog_thread.started.connect(self._watchdog.loop) - self._watchdog_thread.start() - - def _tear_down_watchdog(self) -> None: - self._watchdog.stop_loop() - self._thread_manager.quit_thread(self._watchdog_thread) - del self._watchdog - - def _start_callback(self) -> None: - """ Start the callback procedure. """ - self._callback_function = self._get_callback_function() - self._wavemeter_dll.Instantiate( - high_finesse_constants.cInstNotification, # long ReasonForCall - high_finesse_constants.cNotifyInstallCallbackEx, # long Mode - cast(self._callback_function, POINTER(c_long)), # long P1: function - 0 # long P2: callback thread priority, 0 = standard - ) - self.module_state.lock() - self.log.debug('Started callback procedure.') - - def _stop_callback(self) -> None: - """ Stop the callback procedure. """ - self._wavemeter_dll.Instantiate( - high_finesse_constants.cInstNotification, # long ReasonForCall - high_finesse_constants.cNotifyRemoveCallback, # long mode - cast(self._callback_function, POINTER(c_long)), - # long P1: function - 0) # long P2: callback thread priority, 0 = standard - self._callback_function = None - self.module_state.unlock() - self.log.debug('Stopped callback procedure.') - - def _get_callback_function(self) -> WINFUNCTYPE: - """ - Define the callback procedure that should be called by the DLL every time a new measurement result - is available or any of the wavelength meter's states changes. - :return: callback function - """ - def handle_callback(version, mode: int, intval: int, dblval: float, res1) -> int: - """ - Function called upon wavelength meter state change or if a new measurement result is available. - See wavemeter manual section on CallbackProc for details. - - In this implementation, the new wavelength is converted to the desired unit and - appended to a list together with the current timestamp. - - :param version: Device version number which called the procedure. - Only relevant if multiple wavemeter applications are running. - :param mode: Indicates which state has changed or what new result is available. - :param intval: Contains the time stamp rounded to ms if mode indicates that the new value is in dblval. - If not, it contains the new value itself. - :param dblval: May contain the new value (e.g. wavelength), depending on mode. - :param res1: Mostly meaningless. - :return: 0 - """ - # check if an evil user messed with the manufacturer GUI - if mode == high_finesse_constants.cmiOperation and intval == high_finesse_constants.cStop: - self.log.warning('Wavemeter acquisition was stopped during stream.') - self.error_in_callback = True - return 0 - elif mode == high_finesse_constants.cmiSwitcherMode: - self.log.warning('Wavemeter switcher mode was changed during stream.') - self.error_in_callback = True - return 0 - elif mode == high_finesse_constants.cmiPulseMode: - self.log.warning('Wavemeter pulse mode was changed during stream.') - self.error_in_callback = True - return 0 - - # see if new data is from one of the active channels - ch = high_finesse_constants.cmi_wavelength_n.get(mode) - if ch is None: - # not a new sample, something else happened at the wavemeter - return 0 - - if dblval < 0: - # the wavemeter is either over- or underexposed - # retain error code for further processing - wavelength = dblval - else: - wavelength = 1e-9 * dblval # measurement is in nm - timestamp = 1e-3 * intval # wavemeter records timestamps in ms - for instreamer, channels in self._connected_instream_modules.items(): - if ch in channels: - instreamer.process_new_wavelength(ch, wavelength, timestamp) - return 0 - - _CALLBACK = WINFUNCTYPE(c_int, c_long, c_long, c_long, c_double, c_long) - return _CALLBACK(handle_callback) diff --git a/src/qudi/hardware/wavemeter/high_finesse_wavemeter.py b/src/qudi/hardware/wavemeter/high_finesse_wavemeter.py index 9edb3f8..0af0f7f 100644 --- a/src/qudi/hardware/wavemeter/high_finesse_wavemeter.py +++ b/src/qudi/hardware/wavemeter/high_finesse_wavemeter.py @@ -1,17 +1,8 @@ # -*- coding: utf-8 -*- - """ -This file contains the qudi hardware module for the HighFinesse wavemeter. It implements the -DataInStreamInterface. Communication with the hardware is done via callback functions such that no new data is missed. -Measurement timestamps provided by the hardware are used for sample timing. As an approximation, the timestamps -corresponding to the first active channel are equally used for all channels. Considering the fixed round-trip time -composing the individual channel exposure times and switching times, this implementation should satisfy all -synchronization needs to an extent that is possible to implement on this software level. - -Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this -distribution and on +This module reads data from High Finesse wavemeters -This file is part of qudi. +Copyright (c) 2025, the QuPIDC qudi developers. Qudi is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, @@ -25,438 +16,82 @@ If not, see . """ -import time -from typing import Union, Optional, List, Tuple, Sequence, Any, Dict - import numpy as np -from scipy.constants import lambda2nu from PySide2 import QtCore -from qudi.core.configoption import ConfigOption -from qudi.core.connector import Connector -from qudi.util.mutex import Mutex -from qudi.util.constraints import ScalarConstraint -from qudi.interface.data_instream_interface import DataInStreamInterface, DataInStreamConstraints, StreamingMode, \ - SampleTiming -from qudi.hardware.wavemeter.high_finesse_proxy import HighFinesseProxy -from qudi.hardware.wavemeter.high_finesse_constants import GetFrequencyError +from qudi.interface.simple_wavemeter_interface import SimpleWavemeterInterface +import qudi.hardware.wavemeter.wlmConst as wlmConst +import qudi.hardware.wavemeter.wlmData as wlmData -class HighFinesseWavemeter(DataInStreamInterface): - """ - HighFinesse wavelength meter as an in-streaming device. - - The HighFinesseProxy hardware module is required. It takes care of all communication with the hardware. +class HighFinesseWavemeter(SimpleWavemeterInterface): + """ + Hardware file for high finesse wavemeter + + Assumes the DLL is installed in the system path so does not need to be manually located + Example config for copy-paste: wavemeter: module.Class: 'wavemeter.high_finesse_wavemeter.HighFinesseWavemeter' - connect: - proxy: wavemeter_proxy - options: - channels: - laser: - switch_ch: 1 # channel on the wavemeter switch - unit: 'm' # wavelength (m) or frequency (Hz) - exposure: 10 # exposure time in ms, optional """ - - # declare signals - sigNewWavelength = QtCore.Signal(object) - - _proxy: HighFinesseProxy = Connector(name='proxy', interface='HighFinesseProxy') - - # config options - _wavemeter_ch_config: Dict[str, Dict[str, Any]] = ConfigOption( - name='channels', - default={ - 'default_channel': {'switch_ch': 1, 'unit': 'm', 'exposure': None} - }, - missing='info' - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._lock = Mutex() - - # internal settings - # dictionary with switch channel numbers as keys, channel names as values - self._channel_names: Dict[int, str] = {} - self._channel_units: Dict[int, str] = {} - self._channel_buffer_size = 1024**2 - self._active_switch_channels: Optional[List[int]] = None # list of active switch channel numbers - self._last_measurement_error: Dict[int, float] = {} - - # data buffer - self._wm_start_time: Optional[float] = None - self._data_buffer: Optional[np.ndarray] = None - self._timestamp_buffer: Optional[np.ndarray] = None - self._current_buffer_position = 0 - self._buffer_overflow = False - - # stored hardware constraints - self._constraints: Optional[DataInStreamConstraints] = None - - def on_activate(self) -> None: - # configure wavemeter channels - for ch_name, info in self._wavemeter_ch_config.items(): - ch = info['switch_ch'] - unit = info['unit'] - self._channel_names[ch] = ch_name - - if unit == 'THz' or unit == 'Hz': - self._channel_units[ch] = 'Hz' - elif unit == 'nm' or unit == 'm': - self._channel_units[ch] = 'm' - else: - self.log.warning(f'Invalid unit: {unit}. Valid units are Hz and m. Using m as default.') - self._channel_units[ch] = 'm' - - exp_time = info.get('exposure') - if exp_time is not None: - self._proxy().set_exposure_time(ch, exp_time) - - self._active_switch_channels = list(self._channel_names) - - # set up constraints - sample_rate = self.sample_rate - self._constraints = DataInStreamConstraints( - channel_units={self._channel_names[ch]: self._channel_units[ch] for ch in self._active_switch_channels}, - sample_timing=SampleTiming.TIMESTAMP, - # TODO: implement fixed streaming mode - streaming_modes=[StreamingMode.CONTINUOUS], - data_type=np.float64, - channel_buffer_size=ScalarConstraint(default=1024**2, # 8 MB - bounds=(128, 1024**3), # max = 8 GB - increment=1, - enforce_int=True), - sample_rate=ScalarConstraint(default=sample_rate, - bounds=(0.01, 1e3)) - ) - - def on_deactivate(self) -> None: - self.stop_stream() - - # free memory - self._data_buffer = None - self._timestamp_buffer = None - - @property - def constraints(self) -> DataInStreamConstraints: - """ Read-only property returning the constraints on the settings for this data streamer. """ - return self._constraints - - def start_stream(self) -> None: - """ Start the data acquisition/streaming """ - with self._lock: - if self.module_state() == 'idle': - self.module_state.lock() - self._init_buffers() - self._last_measurement_error = {ch: 0 for ch in self._active_switch_channels} - self._proxy().connect_instreamer(self, self._active_switch_channels) - else: - self.log.warning('Unable to start input stream. It is already running.') - - def stop_stream(self) -> None: - """ Stop the data acquisition/streaming """ - with self._lock: - if self.module_state() == 'locked': - self._proxy().disconnect_instreamer(self) - self._wm_start_time = None - self.module_state.unlock() - else: - self.log.warning('Unable to stop wavemeter input stream as nothing is running.') - - def stop_stream_watchdog(self) -> None: - """Meant to be called from proxy watchdog only. Skips the disconnecting.""" - with self._lock: - if self.module_state() == 'locked': - self._wm_start_time = None - self.module_state.unlock() - else: - self.log.warning('Unable to stop wavemeter input stream as nothing is running.') - - def read_data_into_buffer(self, - data_buffer: np.ndarray, - samples_per_channel: int, - timestamp_buffer: Optional[np.ndarray] = None) -> None: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - Samples of all channels are stored interleaved in contiguous memory. - In case of a multidimensional buffer array, this buffer will be flattened before written - into. - The 1D data_buffer can be unraveled into channel and sample indexing with: - - data_buffer.reshape([, ]) - - The data_buffer array must have the same data type as self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array has to be - provided to be filled with timestamps corresponding to the data_buffer array. It must be - able to hold at least items: - - This function is blocking until the required number of samples has been acquired. + + sigWavelengthUpdated = QtCore.Signal(np.ndarray) # 1-d array of wavelengths in nm + + def on_activate(self): + """ Activate module. """ - self._validate_buffers(data_buffer, timestamp_buffer) - - # wait until requested number of samples is available - while self.available_samples < samples_per_channel: - if self.module_state() != 'locked': - break - # wait for 10 ms - time.sleep(0.01) - - with self._lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - - total_samples = samples_per_channel * len(self.active_channels) - data_buffer[:total_samples] = self._data_buffer[:total_samples] - timestamp_buffer[:samples_per_channel] = self._timestamp_buffer[:samples_per_channel] - self._remove_samples_from_buffer(samples_per_channel) - - def read_available_data_into_buffer(self, - data_buffer: np.ndarray, - timestamp_buffer: Optional[np.ndarray] = None) -> int: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - All samples for each channel are stored in consecutive blocks one after the other. - The number of samples read per channel is returned and can be used to slice out valid data - from the buffer arrays like: - - valid_data = data_buffer[: * ] - valid_timestamps = timestamp_buffer[:] - - See "read_data_into_buffer" documentation for more details. - - This method will read all currently available samples into buffer. If number of available - samples exceeds buffer size, read only as many samples as fit into the buffer. + try: + self._dll = wlmData.LoadDLL() + except OSError as err: + self.log.exception(f'{err}\nPlease check if the wlmData DLL is installed correctly!') + + # Check the number of WLM server instances + if self._dll.GetWLMCount(0) == 0: + self.log.exception('There is no running WLM server instance.') + + + self._data = np.zeros((4, 1)) + + @wlmData.CALLBACK_TYPE + def cb(mode, _intval, dblval): + self._update(mode, _intval, dblval) + + self._cb = cb + + # Install callback function + callback_thread_priority = 2 + self._dll.Instantiate( + wlmConst.cInstNotification, + wlmConst.cNotifyInstallCallback, + self._cb, + callback_thread_priority, + ) + + + def on_deactivate(self): + """ Deactivate module. """ - with self._lock: - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Stream is not running.') - - req_samples_per_channel = self._validate_buffers(data_buffer, timestamp_buffer) - number_of_channels = len(self.active_channels) - samples_per_channel = min(req_samples_per_channel, self.available_samples) - total_samples = number_of_channels * samples_per_channel - - data_buffer[:total_samples] = self._data_buffer[:total_samples] - timestamp_buffer[:samples_per_channel] = self._timestamp_buffer[:samples_per_channel] - self._remove_samples_from_buffer(samples_per_channel) - - return samples_per_channel - - def read_data(self, - samples_per_channel: Optional[int] = None - ) -> Tuple[np.ndarray, Union[np.ndarray, None]]: - """ Read data from the stream buffer into a 1D numpy array and return it. - All samples for each channel are stored in consecutive blocks one after the other. - The returned data_buffer can be unraveled into channel samples with: - - data_buffer.reshape([, samples_per_channel]) - - The numpy array data type is the one defined in self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array will be - returned as well with timestamps corresponding to the data_buffer array. - - If samples_per_channel is omitted all currently available samples are read from buffer. - This method will not return until all requested samples have been read or a timeout occurs. - """ - samples_per_channel = samples_per_channel if samples_per_channel is not None else self.available_samples - total_samples = len(self.active_channels) * samples_per_channel - - data_buffer = np.empty(total_samples, dtype=self.constraints.data_type) - timestamp_buffer = np.empty(samples_per_channel, dtype=np.float64) - self.read_data_into_buffer(data_buffer, samples_per_channel, timestamp_buffer) - - return data_buffer, timestamp_buffer - - def read_single_point(self) -> Tuple[np.ndarray, Union[None, np.float64]]: - """ This method will initiate a single sample read on each configured data channel. - In general this sample may not be acquired simultaneous for all channels and timing in - general can not be assured. Us this method if you want to have a non-timing-critical - snapshot of your current data channel input. - May not be available for all devices. - The returned 1D numpy array will contain one sample for each channel. - - In case of SampleTiming.TIMESTAMP a single numpy.float64 timestamp value will be returned - as well. - """ - if self.module_state() != 'locked': - raise RuntimeError('Unable to read data. Device is not running.') - - with self._lock: - n = len(self.active_channels) - available_samples = self.available_samples - # get the most recent samples for each channel - data = self._data_buffer[n * (available_samples - 1):n * available_samples] - timestamp = self._timestamp_buffer[available_samples - 1] - - return data, timestamp - - @property - def sample_rate(self) -> float: - """ Read-only property returning the currently set sample rate in Hz. - For SampleTiming.CONSTANT this is the sample rate of the hardware, for any other timing mode - this property represents only a hint to the actual hardware timebase and can not be - considered accurate. - - For the wavemeter, it is estimated by the exposure times per channel and switching times if - more than one channel is active. - """ - return self._proxy().sample_rate() - - @property - def streaming_mode(self) -> StreamingMode: - """ Read-only property returning the currently configured StreamingMode Enum """ - return StreamingMode.CONTINUOUS - - @property - def active_channels(self) -> List[str]: - """ Read-only property returning the currently configured active channel names """ - ch_names = [self._channel_names[ch] for ch in self._active_switch_channels] - return ch_names - - @property - def available_samples(self) -> int: - """ - Read-only property to return the currently available number of samples per channel ready - to read from buffer. - """ - if self.module_state() != 'locked': - return 0 - - # all channels must have been read out in order to count as an available sample - return self._current_buffer_position // len(self.active_channels) - - @property - def channel_buffer_size(self) -> int: - """ Read-only property returning the currently set buffer size in samples per channel. - The total buffer size in bytes can be estimated by: - * * numpy.nbytes[] - - For StreamingMode.FINITE this will also be the total number of samples to acquire per - channel. - """ - return self._channel_buffer_size - - def configure(self, - active_channels: Sequence[str], - streaming_mode: Union[StreamingMode, int], - channel_buffer_size: int, - sample_rate: float) -> None: - """ Configure a data stream. See read-only properties for information on each parameter. """ - if self.module_state() == 'locked': - raise RuntimeError('Unable to configure data stream while it is already running') - - if active_channels is not None: - self._active_switch_channels = [] - for ch in active_channels: - if ch in self._wavemeter_ch_config: - self._active_switch_channels.append(self._wavemeter_ch_config[ch]['switch_ch']) - else: - raise ValueError(f'Channel {ch} is not set up in the config file. Available channels ' - f'are {list(self._channel_names.keys())}.') - - if streaming_mode is not None and streaming_mode.value != StreamingMode.CONTINUOUS.value: - self.log.warning('Only continuous streaming is supported, ignoring this setting.') - - if channel_buffer_size is not None: - self.constraints.channel_buffer_size.is_valid(channel_buffer_size) - self._channel_buffer_size = channel_buffer_size - - def process_new_wavelength(self, ch, wavelength, timestamp): - with self._lock: - try: - i = self._active_switch_channels.index(ch) - except ValueError: - # channel is not active on this instreamer - return - - if self._last_measurement_error[ch] != 0: - if wavelength > 0: - # reset error flag - self._last_measurement_error[ch] = 0 - - if wavelength <= 0: - # negative values indicate an error - if self._last_measurement_error[ch] != wavelength: - # error is new - self._last_measurement_error[ch] = wavelength - self.log.warning(f'The last wavemeter measurement of channel {ch} was unsuccessful ' - f'due to {GetFrequencyError(wavelength).name}.') - wavelength = np.nan - - with self._lock: - number_of_channels = len(self.active_channels) - current_timestamp_buffer_position = self._current_buffer_position // number_of_channels - if current_timestamp_buffer_position >= self.channel_buffer_size: - self._buffer_overflow = True - raise OverflowError( - 'Streaming buffer encountered an overflow while receiving a callback from the wavemeter. ' - 'Please increase the buffer size or speed up data reading.' - ) - - # unit conversion - if self._channel_units[ch] == 'Hz': - converted_value = lambda2nu(wavelength) - else: - converted_value = wavelength - - # check if this is the first time this callback runs during a stream - if self._wm_start_time is None: - # set the timing offset to the start of the stream - self._wm_start_time = timestamp - - if i != self._current_buffer_position % number_of_channels: - # discard the sample if a sample was missed before and the buffer position is off - return - - timestamp -= self._wm_start_time - # insert the new data into the buffers - self._data_buffer[self._current_buffer_position] = converted_value - if i == 0: - # only record the timestamp of the first active channel - self._timestamp_buffer[current_timestamp_buffer_position] = timestamp - self._current_buffer_position += 1 - - self.sigNewWavelength.emit(converted_value) - - def _init_buffers(self) -> None: - """ Initialize buffers and the current buffer position marker. """ - n = len(self._active_switch_channels) - self._data_buffer = np.zeros(n * self._channel_buffer_size, dtype=self.constraints.data_type) - self._timestamp_buffer = np.zeros(self._channel_buffer_size, dtype=np.float64) - self._current_buffer_position = 0 - self._buffer_overflow = False - - def _remove_samples_from_buffer(self, samples_per_channel: int) -> None: - """ - Remove samples that have been read from buffer to make space for new samples. - :param samples_per_channel: number of samples per channel to clear off the buffer - :return: None + self._dll.Instantiate(wlmConst.cInstNotification, wlmConst.cNotifyRemoveCallback, None, 0) + + + def get_wavelengths(self) -> np.ndarray: + """ Get the wavelengths from the wavemeter """ - total_samples = len(self.active_channels) * samples_per_channel - self._data_buffer = np.roll(self._data_buffer, -total_samples) - self._timestamp_buffer = np.roll(self._timestamp_buffer, -samples_per_channel) - self._current_buffer_position -= total_samples - - def _validate_buffers(self, - data_buffer: np.ndarray, - timestamp_buffer: np.ndarray) -> Tuple[int, Union[int, Any]]: - """ Validate arguments for read_[available]_data_into_buffer methods. """ - if not isinstance(data_buffer, np.ndarray) or data_buffer.dtype != self.constraints.data_type: - raise TypeError(f'data_buffer must be numpy.ndarray with dtype {self.constraints.data_type}.') - - if not isinstance(timestamp_buffer, np.ndarray) or timestamp_buffer.dtype != np.float64: - raise TypeError(f'timestamp_buffer must be provided for the wavemeter and ' - f'it must be a numpy.ndarray with dtype np.float64.') - - number_of_channels = len(self.active_channels) - samples_per_channel = data_buffer.size // number_of_channels - - if timestamp_buffer.size != samples_per_channel: - raise ValueError(f'timestamp_buffer must be exactly of length data_buffer // ') - - return samples_per_channel + return self._data + + def _update(self, mode, _intval, dblval): + i = -1 + if mode == wlmConst.cmiWavelength1: + i = 0 + elif mode == wlmConst.cmiWavelength2: + i = 1 + elif mode == wlmConst.cmiWavelength3: + i = 2 + elif mode == wlmConst.cmiWavelength4: + i = 3 + + self._data[i] = dblval + self.sigWavelengthUpdated.emit(self._data) + diff --git a/src/qudi/hardware/wavemeter/high_finesse_wrapper.py b/src/qudi/hardware/wavemeter/high_finesse_wrapper.py deleted file mode 100644 index b2fd04b..0000000 --- a/src/qudi/hardware/wavemeter/high_finesse_wrapper.py +++ /dev/null @@ -1,664 +0,0 @@ -###################################################################################################### -# @package wlmData -# @file wlmData.py -# @copyright HighFinesse GmbH. -# @date 2020.06.02 -# @version 0.4 -# -# Homepage: http://www.highfinesse.com/ -# -# @brief Python wrapper for wlmData.dll. -# -# Changelog: -# ---------- -# 2018.09.12 -# v0.1 - Initial release -# 2018.09.14 -# v0.2 - Constant values added -# 2018.09.15 -# v0.3 - Constant values separated to wlmConst.py, LoadDLL() added -# 2020.06.02 -# v0.4 - GetPattern... and GetAnalysisData argtypes adapted -# / - -import warnings -from ctypes import c_bool, c_double, c_char_p, c_long, c_longlong, c_short, c_ulong, c_ushort, POINTER, windll - -MIN_VERSION = 6491 - - -def load_dll(dll_path='wlmData.dll'): - dll = windll.LoadLibrary(dll_path) - return dll - - -def setup_dll(dll): - # LONG_PTR Instantiate(long RFC, long Mode, LONG_PTR P1, long P2) - dll.Instantiate.argtypes = [c_long, c_long, POINTER(c_long), c_long] - dll.Instantiate.restype = POINTER(c_long) - - # long WaitForWLMEvent(lref Mode, lref IntVal, dref DblVal) - dll.WaitForWLMEvent.argtypes = [POINTER(c_long), POINTER(c_long), POINTER(c_double)] - dll.WaitForWLMEvent.restype = c_long - - # long WaitForWLMEventEx(lref Ver, lref Mode, lref IntVal, dref DblVal, lref Res1) - dll.WaitForWLMEventEx.argtypes = [POINTER(c_long), POINTER(c_long), POINTER(c_long), POINTER(c_double), - POINTER(c_long)] - dll.WaitForWLMEventEx.restype = c_long - - # long WaitForNextWLMEvent(lref Mode, lref IntVal, dref DblVal) - dll.WaitForNextWLMEvent.argtypes = [POINTER(c_long), POINTER(c_long), POINTER(c_double)] - dll.WaitForNextWLMEvent.restype = c_long - - # long WaitForNextWLMEventEx(lref Ver, lref Mode, lref IntVal, dref DblVal, lref Res1) - dll.WaitForNextWLMEventEx.argtypes = [POINTER(c_long), POINTER(c_long), POINTER(c_long), POINTER(c_double), - POINTER(c_long)] - dll.WaitForNextWLMEventEx.restype = c_long - - # void ClearWLMEvents(void) - dll.ClearWLMEvents.argtypes = [] - dll.ClearWLMEvents.restype = None - - # long ControlWLM(long Action, LONG_PTR App, long Ver) - dll.ControlWLM.argtypes = [c_long, POINTER(c_long), c_long] - dll.ControlWLM.restype = c_long - - # long ControlWLMEx(long Action, LONG_PTR App, long Ver, long Delay, long Res) - dll.ControlWLMEx.argtypes = [c_long, POINTER(c_long), c_long, c_long, c_long] - dll.ControlWLMEx.restype = c_long - - # __int64 SynchroniseWLM(long Mode, __int64 TS) - dll.SynchroniseWLM.argtypes = [c_long, c_longlong] - dll.SynchroniseWLM.restype = c_longlong - - # long SetMeasurementDelayMethod(long Mode, long Delay) - dll.SetMeasurementDelayMethod.argtypes = [c_long, c_long] - dll.SetMeasurementDelayMethod.restype = c_long - - # long SetWLMPriority(long PPC, long Res1, long Res2) - dll.SetWLMPriority.argtypes = [c_long, c_long, c_long] - dll.SetWLMPriority.restype = c_long - - # long PresetWLMIndex(long Ver) - dll.PresetWLMIndex.argtypes = [c_long] - dll.PresetWLMIndex.restype = c_long - - # long GetWLMVersion(long Ver) - dll.GetWLMVersion.argtypes = [c_long] - dll.GetWLMVersion.restype = c_long - - # long GetWLMIndex(long Ver) - dll.GetWLMIndex.argtypes = [c_long] - dll.GetWLMIndex.restype = c_long - - # long GetWLMCount(long V) - dll.GetWLMCount.argtypes = [c_long] - dll.GetWLMCount.restype = c_long - - # double GetWavelength(double WL) - dll.GetWavelength.argtypes = [c_double] - dll.GetWavelength.restype = c_double - - # double GetWavelength2(double WL2) - dll.GetWavelength2.argtypes = [c_double] - dll.GetWavelength2.restype = c_double - - # double GetWavelengthNum(long num, double WL) - dll.GetWavelengthNum.argtypes = [c_long, c_double] - dll.GetWavelengthNum.restype = c_double - - # double GetCalWavelength(long ba, double WL) - dll.GetCalWavelength.argtypes = [c_long, c_double] - dll.GetCalWavelength.restype = c_double - - # double GetCalibrationEffect(double CE) - dll.GetCalibrationEffect.argtypes = [c_double] - dll.GetCalibrationEffect.restype = c_double - - # double GetFrequency(double F) - dll.GetFrequency.argtypes = [c_double] - dll.GetFrequency.restype = c_double - - # double GetFrequency2(double F2) - dll.GetFrequency2.argtypes = [c_double] - dll.GetFrequency2.restype = c_double - - # double GetFrequencyNum(long num, double F) - dll.GetFrequencyNum.argtypes = [c_long, c_double] - dll.GetFrequencyNum.restype = c_double - - # double GetLinewidth(long Index, double LW) - dll.GetLinewidth.argtypes = [c_long, c_double] - dll.GetLinewidth.restype = c_double - - # double GetLinewidthNum(long num, double LW) - dll.GetLinewidthNum.argtypes = [c_long, c_double] - dll.GetLinewidthNum.restype = c_double - - # double GetDistance(double D) - dll.GetDistance.argtypes = [c_double] - dll.GetDistance.restype = c_double - - # double GetAnalogIn(double AI) - dll.GetAnalogIn.argtypes = [c_double] - dll.GetAnalogIn.restype = c_double - - # double GetTemperature(double T) - dll.GetTemperature.argtypes = [c_double] - dll.GetTemperature.restype = c_double - - # long SetTemperature(double T) - dll.SetTemperature.argtypes = [c_double] - dll.SetTemperature.restype = c_long - - # double GetPressure(double P) - dll.GetPressure.argtypes = [c_double] - dll.GetPressure.restype = c_double - - # long SetPressure(long Mode, double P) - dll.SetPressure.argtypes = [c_long, c_double] - dll.SetPressure.restype = c_long - - # double GetExternalInput(long Index, double I) - dll.GetExternalInput.argtypes = [c_long, c_double] - dll.GetExternalInput.restype = c_double - - # long SetExternalInput(long Index, double I) - dll.SetExternalInput.argtypes = [c_long, c_double] - dll.SetExternalInput.restype = c_long - - # long GetExtraSetting(long Index, lref lGet, dref dGet, sref sGet) - dll.GetExtraSetting.argtypes = [c_long, POINTER(c_long), POINTER(c_double), c_char_p] - dll.GetExtraSetting.restype = c_long - - # long SetExtraSetting(long Index, long lSet, double dSet, sref sSet) - dll.SetExtraSetting.argtypes = [c_long, c_long, c_double, c_char_p] - dll.SetExtraSetting.restype = c_long - - # unsigned short GetExposure(unsigned short E) - dll.GetExposure.argtypes = [c_ushort] - dll.GetExposure.restype = c_ushort - - # long SetExposure(unsigned short E) - dll.SetExposure.argtypes = [c_ushort] - dll.SetExposure.restype = c_long - - # unsigned short GetExposure2(unsigned short E2) - dll.GetExposure2.argtypes = [c_ushort] - dll.GetExposure2.restype = c_ushort - - # long SetExposure2(unsigned short E2) - dll.SetExposure2.argtypes = [c_ushort] - dll.SetExposure2.restype = c_long - - # long GetExposureNum(long num, long arr, long E) - dll.GetExposureNum.argtypes = [c_long, c_long, c_long] - dll.GetExposureNum.restype = c_long - - # long SetExposureNum(long num, long arr, long E) - dll.SetExposureNum.argtypes = [c_long, c_long, c_long] - dll.SetExposureNum.restype = c_long - - # double GetExposureNumEx(long num, long arr, double E) - dll.GetExposureNumEx.argtypes = [c_long, c_long, c_double] - dll.GetExposureNumEx.restype = c_double - - # long SetExposureNumEx(long num, long arr, double E) - dll.SetExposureNumEx.argtypes = [c_long, c_long, c_double] - dll.SetExposureNumEx.restype = c_long - - # bool GetExposureMode(bool EM) - dll.GetExposureMode.argtypes = [c_bool] - dll.GetExposureMode.restype = c_bool - - # long SetExposureMode(bool EM) - dll.SetExposureMode.argtypes = [c_bool] - dll.SetExposureMode.restype = c_long - - # long GetExposureModeNum(long num, bool EM) - dll.GetExposureModeNum.argtypes = [c_long, c_bool] - dll.GetExposureModeNum.restype = c_long - - # long SetExposureModeNum(long num, bool EM) - dll.SetExposureModeNum.argtypes = [c_long, c_bool] - dll.SetExposureModeNum.restype = c_long - - # long GetExposureRange(long ER) - dll.GetExposureRange.argtypes = [c_long] - dll.GetExposureRange.restype = c_long - - # long GetAutoExposureSetting(long num, long AES, lref iVal, dref dVal) - dll.GetAutoExposureSetting.argtypes = [c_long, c_long, POINTER(c_long), POINTER(c_double)] - dll.GetAutoExposureSetting.restype = c_long - - # long SetAutoExposureSetting(long num, long AES, long iVal, double dVal) - dll.SetAutoExposureSetting.argtypes = [c_long, c_long, c_long, c_double] - dll.SetAutoExposureSetting.restype = c_long - - # unsigned short GetResultMode(unsigned short RM) - dll.GetResultMode.argtypes = [c_ushort] - dll.GetResultMode.restype = c_ushort - - # long SetResultMode(unsigned short RM) - dll.SetResultMode.argtypes = [c_ushort] - dll.SetResultMode.restype = c_long - - # unsigned short GetRange(unsigned short R) - dll.GetRange.argtypes = [c_ushort] - dll.GetRange.restype = c_ushort - - # long SetRange(unsigned short R) - dll.SetRange.argtypes = [c_ushort] - dll.SetRange.restype = c_long - - # unsigned short GetPulseMode(unsigned short PM) - dll.GetPulseMode.argtypes = [c_ushort] - dll.GetPulseMode.restype = c_ushort - - # long SetPulseMode(unsigned short PM) - dll.SetPulseMode.argtypes = [c_ushort] - dll.SetPulseMode.restype = c_long - - # long GetPulseDelay(long PD) - dll.GetPulseDelay.argtypes = [c_long] - dll.GetPulseDelay.restype = c_long - - # long SetPulseDelay(long PD) - dll.SetPulseDelay.argtypes = [c_long] - dll.SetPulseDelay.restype = c_long - - # unsigned short GetWideMode(unsigned short WM) - dll.GetWideMode.argtypes = [c_ushort] - dll.GetWideMode.restype = c_ushort - - # long SetWideMode(unsigned short WM) - dll.SetWideMode.argtypes = [c_ushort] - dll.SetWideMode.restype = c_long - - # long GetDisplayMode(long DM) - dll.GetDisplayMode.argtypes = [c_long] - dll.GetDisplayMode.restype = c_long - - # long SetDisplayMode(long DM) - dll.SetDisplayMode.argtypes = [c_long] - dll.SetDisplayMode.restype = c_long - - # bool GetFastMode(bool FM) - dll.GetFastMode.argtypes = [c_bool] - dll.GetFastMode.restype = c_bool - - # long SetFastMode(bool FM) - dll.SetFastMode.argtypes = [c_bool] - dll.SetFastMode.restype = c_long - - # bool GetLinewidthMode(bool LM) - dll.GetLinewidthMode.argtypes = [c_bool] - dll.GetLinewidthMode.restype = c_bool - - # long SetLinewidthMode(bool LM) - dll.SetLinewidthMode.argtypes = [c_bool] - dll.SetLinewidthMode.restype = c_long - - # bool GetDistanceMode(bool DM) - dll.GetDistanceMode.argtypes = [c_bool] - dll.GetDistanceMode.restype = c_bool - - # long SetDistanceMode(bool DM) - dll.SetDistanceMode.argtypes = [c_bool] - dll.SetDistanceMode.restype = c_long - - # long GetSwitcherMode(long SM) - dll.GetSwitcherMode.argtypes = [c_long] - dll.GetSwitcherMode.restype = c_long - - # long SetSwitcherMode(long SM) - dll.SetSwitcherMode.argtypes = [c_long] - dll.SetSwitcherMode.restype = c_long - - # long GetSwitcherChannel(long CH) - dll.GetSwitcherChannel.argtypes = [c_long] - dll.GetSwitcherChannel.restype = c_long - - # long SetSwitcherChannel(long CH) - dll.SetSwitcherChannel.argtypes = [c_long] - dll.SetSwitcherChannel.restype = c_long - - # long GetSwitcherSignalStates(long Signal, lref Use, lref Show) - dll.GetSwitcherSignalStates.argtypes = [c_long, POINTER(c_long), POINTER(c_long)] - dll.GetSwitcherSignalStates.restype = c_long - - # long SetSwitcherSignalStates(long Signal, long Use, long Show) - dll.SetSwitcherSignalStates.argtypes = [c_long, c_long, c_long] - dll.SetSwitcherSignalStates.restype = c_long - - # long SetSwitcherSignal(long Signal, long Use, long Show) - dll.SetSwitcherSignal.argtypes = [c_long, c_long, c_long] - dll.SetSwitcherSignal.restype = c_long - - # long GetAutoCalMode(long ACM) - dll.GetAutoCalMode.argtypes = [c_long] - dll.GetAutoCalMode.restype = c_long - - # long SetAutoCalMode(long ACM) - dll.SetAutoCalMode.argtypes = [c_long] - dll.SetAutoCalMode.restype = c_long - - # long GetAutoCalSetting(long ACS, lref val, long Res1, lref Res2) - dll.GetAutoCalSetting.argtypes = [c_long, POINTER(c_long), c_long, POINTER(c_long)] - dll.GetAutoCalSetting.restype = c_long - - # long SetAutoCalSetting(long ACS, long val, long Res1, long Res2) - dll.SetAutoCalSetting.argtypes = [c_long, c_long, c_long, c_long] - dll.SetAutoCalSetting.restype = c_long - - # long GetActiveChannel(long Mode, lref Port, long Res1) - dll.GetActiveChannel.argtypes = [c_long, POINTER(c_long), c_long] - dll.GetActiveChannel.restype = c_long - - # long SetActiveChannel(long Mode, long Port, long CH, long Res1) - dll.SetActiveChannel.argtypes = [c_long, c_long, c_long, c_long] - dll.SetActiveChannel.restype = c_long - - # long GetChannelsCount(long C) - dll.GetChannelsCount.argtypes = [c_long] - dll.GetChannelsCount.restype = c_long - - # unsigned short GetOperationState(unsigned short OS) - dll.GetOperationState.argtypes = [c_ushort] - dll.GetOperationState.restype = c_ushort - - # long Operation(unsigned short Op) - dll.Operation.argtypes = [c_ushort] - dll.Operation.restype = c_long - - # long SetOperationFile(sref lpFile) - dll.SetOperationFile.argtypes = [c_char_p] - dll.SetOperationFile.restype = c_long - - # long Calibration(long Type, long Unit, double Value, long Channel) - dll.Calibration.argtypes = [c_long, c_long, c_double, c_long] - dll.Calibration.restype = c_long - - # long RaiseMeasurementEvent(long Mode) - dll.RaiseMeasurementEvent.argtypes = [c_long] - dll.RaiseMeasurementEvent.restype = c_long - - # long TriggerMeasurement(long Action) - dll.TriggerMeasurement.argtypes = [c_long] - dll.TriggerMeasurement.restype = c_long - - # long GetTriggerState(long TS) - dll.GetTriggerState.argtypes = [c_long] - dll.GetTriggerState.restype = c_long - - # long GetInterval(long I) - dll.GetInterval.argtypes = [c_long] - dll.GetInterval.restype = c_long - - # long SetInterval(long I) - dll.SetInterval.argtypes = [c_long] - dll.SetInterval.restype = c_long - - # bool GetIntervalMode(bool IM) - dll.GetIntervalMode.argtypes = [c_bool] - dll.GetIntervalMode.restype = c_bool - - # long SetIntervalMode(bool IM) - dll.SetIntervalMode.argtypes = [c_bool] - dll.SetIntervalMode.restype = c_long - - # long GetBackground(long BG) - dll.GetBackground.argtypes = [c_long] - dll.GetBackground.restype = c_long - - # long SetBackground(long BG) - dll.SetBackground.argtypes = [c_long] - dll.SetBackground.restype = c_long - - # long GetAveragingSettingNum(long num, long AS, long Value) - dll.GetAveragingSettingNum.argtypes = [c_long, c_long, c_long] - dll.GetAveragingSettingNum.restype = c_long - - # long SetAveragingSettingNum(long num, long AS, long Value) - dll.SetAveragingSettingNum.argtypes = [c_long, c_long, c_long] - dll.SetAveragingSettingNum.restype = c_long - - # bool GetLinkState(bool LS) - dll.GetLinkState.argtypes = [c_bool] - dll.GetLinkState.restype = c_bool - - # long SetLinkState(bool LS) - dll.SetLinkState.argtypes = [c_bool] - dll.SetLinkState.restype = c_long - - # void LinkSettingsDlg(void) - dll.LinkSettingsDlg.argtypes = [] - dll.LinkSettingsDlg.restype = None - - # long GetPatternItemSize(long Index) - dll.GetPatternItemSize.argtypes = [c_long] - dll.GetPatternItemSize.restype = c_long - - # long GetPatternItemCount(long Index) - dll.GetPatternItemCount.argtypes = [c_long] - dll.GetPatternItemCount.restype = c_long - - # ULONG_PTR GetPattern(long Index) - dll.GetPattern.argtypes = [c_long] - dll.GetPattern.restype = POINTER(c_ulong) - - # ULONG_PTR GetPatternNum(long Chn, long Index) - dll.GetPatternNum.argtypes = [c_long, c_long] - dll.GetPatternNum.restype = POINTER(c_ulong) - - # long GetPatternData(long Index, ULONG_PTR PArray) - dll.GetPatternData.argtypes = [c_long, POINTER(c_short)] - dll.GetPatternData.restype = c_long - - # long GetPatternDataNum(long Chn, long Index, ULONG_PTR PArray) - dll.GetPatternDataNum.argtypes = [c_long, c_long, POINTER(c_short)] - dll.GetPatternDataNum.restype = c_long - - # long SetPattern(long Index, long iEnable) - dll.SetPattern.argtypes = [c_long, c_long] - dll.SetPattern.restype = c_long - - # long SetPatternData(long Index, ULONG_PTR PArray) - dll.SetPatternData.argtypes = [c_long, POINTER(c_ulong)] - dll.SetPatternData.restype = c_long - - # bool GetAnalysisMode(bool AM) - dll.GetAnalysisMode.argtypes = [c_bool] - dll.GetAnalysisMode.restype = c_bool - - # long SetAnalysisMode(bool AM) - dll.SetAnalysisMode.argtypes = [c_bool] - dll.SetAnalysisMode.restype = c_long - - # long GetAnalysisItemSize(long Index) - dll.GetAnalysisItemSize.argtypes = [c_long] - dll.GetAnalysisItemSize.restype = c_long - - # long GetAnalysisItemCount(long Index) - dll.GetAnalysisItemCount.argtypes = [c_long] - dll.GetAnalysisItemCount.restype = c_long - - # ULONG_PTR GetAnalysis(long Index) - dll.GetAnalysis.argtypes = [c_long] - dll.GetAnalysis.restype = POINTER(c_ulong) - - # long GetAnalysisData(long Index, ULONG_PTR PArray) - dll.GetAnalysisData.argtypes = [c_long, POINTER(c_double)] - dll.GetAnalysisData.restype = c_long - - # long SetAnalysis(long Index, long iEnable) - dll.SetAnalysis.argtypes = [c_long, c_long] - dll.SetAnalysis.restype = c_long - - # long GetMinPeak(long M1) - dll.GetMinPeak.argtypes = [c_long] - dll.GetMinPeak.restype = c_long - - # long GetMinPeak2(long M2) - dll.GetMinPeak2.argtypes = [c_long] - dll.GetMinPeak2.restype = c_long - - # long GetMaxPeak(long X1) - dll.GetMaxPeak.argtypes = [c_long] - dll.GetMaxPeak.restype = c_long - - # long GetMaxPeak2(long X2) - dll.GetMaxPeak2.argtypes = [c_long] - dll.GetMaxPeak2.restype = c_long - - # long GetAvgPeak(long A1) - dll.GetAvgPeak.argtypes = [c_long] - dll.GetAvgPeak.restype = c_long - - # long GetAvgPeak2(long A2) - dll.GetAvgPeak2.argtypes = [c_long] - dll.GetAvgPeak2.restype = c_long - - # long SetAvgPeak(long PA) - dll.SetAvgPeak.argtypes = [c_long] - dll.SetAvgPeak.restype = c_long - - # long GetAmplitudeNum(long num, long Index, long A) - dll.GetAmplitudeNum.argtypes = [c_long, c_long, c_long] - dll.GetAmplitudeNum.restype = c_long - - # double GetIntensityNum(long num, double I) - dll.GetIntensityNum.argtypes = [c_long, c_double] - dll.GetIntensityNum.restype = c_double - - # double GetPowerNum(long num, double P) - dll.GetPowerNum.argtypes = [c_long, c_double] - dll.GetPowerNum.restype = c_double - - # unsigned short GetDelay(unsigned short D) - dll.GetDelay.argtypes = [c_ushort] - dll.GetDelay.restype = c_ushort - - # long SetDelay(unsigned short D) - dll.SetDelay.argtypes = [c_ushort] - dll.SetDelay.restype = c_long - - # unsigned short GetShift(unsigned short S) - dll.GetShift.argtypes = [c_ushort] - dll.GetShift.restype = c_ushort - - # long SetShift(unsigned short S) - dll.SetShift.argtypes = [c_ushort] - dll.SetShift.restype = c_long - - # unsigned short GetShift2(unsigned short S2) - dll.GetShift2.argtypes = [c_ushort] - dll.GetShift2.restype = c_ushort - - # long SetShift2(unsigned short S2) - dll.SetShift2.argtypes = [c_ushort] - dll.SetShift2.restype = c_long - - # bool GetDeviationMode(bool DM) - dll.GetDeviationMode.argtypes = [c_bool] - dll.GetDeviationMode.restype = c_bool - - # long SetDeviationMode(bool DM) - dll.SetDeviationMode.argtypes = [c_bool] - dll.SetDeviationMode.restype = c_long - - # double GetDeviationReference(double DR) - dll.GetDeviationReference.argtypes = [c_double] - dll.GetDeviationReference.restype = c_double - - # long SetDeviationReference(double DR) - dll.SetDeviationReference.argtypes = [c_double] - dll.SetDeviationReference.restype = c_long - - # long GetDeviationSensitivity(long DS) - dll.GetDeviationSensitivity.argtypes = [c_long] - dll.GetDeviationSensitivity.restype = c_long - - # long SetDeviationSensitivity(long DS) - dll.SetDeviationSensitivity.argtypes = [c_long] - dll.SetDeviationSensitivity.restype = c_long - - # double GetDeviationSignal(double DS) - dll.GetDeviationSignal.argtypes = [c_double] - dll.GetDeviationSignal.restype = c_double - - # double GetDeviationSignalNum(long Port, double DS) - dll.GetDeviationSignalNum.argtypes = [c_long, c_double] - dll.GetDeviationSignalNum.restype = c_double - - # long SetDeviationSignal(double DS) - dll.SetDeviationSignal.argtypes = [c_double] - dll.SetDeviationSignal.restype = c_long - - # long SetDeviationSignalNum(long Port, double DS) - dll.SetDeviationSignalNum.argtypes = [c_long, c_double] - dll.SetDeviationSignalNum.restype = c_long - - # double RaiseDeviationSignal(long iType, double dSignal) - dll.RaiseDeviationSignal.argtypes = [c_long, c_double] - dll.RaiseDeviationSignal.restype = c_double - - # long GetPIDCourse(sref PIDC) - dll.GetPIDCourse.argtypes = [c_char_p] - dll.GetPIDCourse.restype = c_long - - # long SetPIDCourse(sref PIDC) - dll.SetPIDCourse.argtypes = [c_char_p] - dll.SetPIDCourse.restype = c_long - - # long GetPIDCourseNum(long Port, sref PIDC) - dll.GetPIDCourseNum.argtypes = [c_long, c_char_p] - dll.GetPIDCourseNum.restype = c_long - - # long SetPIDCourseNum(long Port, sref PIDC) - dll.SetPIDCourseNum.argtypes = [c_long, c_char_p] - dll.SetPIDCourseNum.restype = c_long - - # long GetPIDSetting(long PS, long Port, lref iSet, dref dSet) - dll.GetPIDSetting.argtypes = [c_long, c_long, POINTER(c_long), POINTER(c_double)] - dll.GetPIDSetting.restype = c_long - - # long SetPIDSetting(long PS, long Port, long iSet, double dSet) - dll.SetPIDSetting.argtypes = [c_long, c_long, c_long, c_double] - dll.SetPIDSetting.restype = c_long - - # long GetLaserControlSetting(long PS, long Port, lref iSet, dref dSet, sref sSet) - dll.GetLaserControlSetting.argtypes = [c_long, c_long, POINTER(c_long), POINTER(c_double), c_char_p] - dll.GetLaserControlSetting.restype = c_long - - # long SetLaserControlSetting(long PS, long Port, long iSet, double dSet, sref sSet) - dll.SetLaserControlSetting.argtypes = [c_long, c_long, c_long, c_double, c_char_p] - dll.SetLaserControlSetting.restype = c_long - - # long ClearPIDHistory(long Port) - dll.ClearPIDHistory.argtypes = [c_long] - dll.ClearPIDHistory.restype = c_long - - # double ConvertUnit(double Val, long uFrom, long uTo) - dll.ConvertUnit.argtypes = [c_double, c_long, c_long] - dll.ConvertUnit.restype = c_double - - # double ConvertDeltaUnit(double Base, double Delta, long uBase, long uFrom, long uTo) - dll.ConvertDeltaUnit.argtypes = [c_double, c_double, c_long, c_long, c_long] - dll.ConvertDeltaUnit.restype = c_double - - # bool GetReduced(bool R) - dll.GetReduced.argtypes = [c_bool] - dll.GetReduced.restype = c_bool - - # long SetReduced(bool R) - dll.SetReduced.argtypes = [c_bool] - dll.SetReduced.restype = c_long - - # unsigned short GetScale(unsigned short S) - dll.GetScale.argtypes = [c_ushort] - dll.GetScale.restype = c_ushort - - # long SetScale(unsigned short S) - dll.SetScale.argtypes = [c_ushort] - dll.SetScale.restype = c_long diff --git a/src/qudi/hardware/wavemeter/high_finesse_constants.py b/src/qudi/hardware/wavemeter/wlmConst.py similarity index 51% rename from src/qudi/hardware/wavemeter/high_finesse_constants.py rename to src/qudi/hardware/wavemeter/wlmConst.py index 1d48a1a..52aa605 100644 --- a/src/qudi/hardware/wavemeter/high_finesse_constants.py +++ b/src/qudi/hardware/wavemeter/wlmConst.py @@ -1,24 +1,9 @@ -###################################################################################################### -# @package wlmData -# @file wlmConst.py -# @copyright HighFinesse GmbH. -# @date 2018.09.15 -# @version 0.1 -# -# Homepage: http://www.highfinesse.com/ -# -# @brief Constant values for wlmData.dll API. -# -# Changelog: -# ---------- -# 2018.09.15 -# v0.1 - Initial release -#/ - -from enum import Enum - -## ########### Constants ############################################## -## Instantiating Constants for 'RFC' parameter +""" +wlmData API constants generated from wlmData.h on 2024-11-12 +""" +# pylint: disable=invalid-name + +# Instantiating Constants for 'RFC' parameter cInstCheckForWLM = -1 cInstResetCalc = 0 cInstReturnMode = cInstResetCalc @@ -28,8 +13,9 @@ cInstControlWLM = 3 cInstControlDelay = 4 cInstControlPriority = 5 +cInstNetworkControl = 6 -## Notification Constants for 'Mode' parameter +# Notification Constants for 'Mode' parameter cNotifyInstallCallback = 0 cNotifyRemoveCallback = 1 cNotifyInstallWaitEvent = 2 @@ -37,32 +23,38 @@ cNotifyInstallCallbackEx = 4 cNotifyInstallWaitEventEx = 5 - -class ResultError(Enum): - """ ResultError Constants of Set...-functions """ - NoErr = 0 - WlmMissing = -1 - CouldNotSet = -2 - ParmOutOfRange = -3 - WlmOutOfResources = -4 - WlmInternalError = -5 - NotAvailable = -6 - WlmBusy = -7 - NotInMeasurementMode = -8 - OnlyInMeasurementMode = -9 - ChannelNotAvailable = -10 - ChannelTemporarilyNotAvailable = -11 - CalOptionNotAvailable = -12 - CalWavelengthOutOfRange = -13 - BadCalibrationSignal = -14 - UnitNotAvailable = -15 - FileNotFound = -16 - FileCreation = -17 - TriggerPending = -18 - TriggerWaiting = -19 - NoLegitimation = -20 - -## Mode Constants for Callback-Export and WaitForWLMEvent-function +# ResultError Constants of Set...-functions +ResERR_NoErr = 0 +ResERR_WlmMissing = -1 +ResERR_CouldNotSet = -2 +ResERR_ParmOutOfRange = -3 +ResERR_WlmOutOfResources = -4 +ResERR_WlmInternalError = -5 +ResERR_NotAvailable = -6 +ResERR_WlmBusy = -7 +ResERR_NotInMeasurementMode = -8 +ResERR_OnlyInMeasurementMode = -9 +ResERR_ChannelNotAvailable = -10 +ResERR_ChannelTemporarilyNotAvailable = -11 +ResERR_CalOptionNotAvailable = -12 +ResERR_CalWavelengthOutOfRange = -13 +ResERR_BadCalibrationSignal = -14 +ResERR_UnitNotAvailable = -15 +ResERR_FileNotFound = -16 +ResERR_FileCreation = -17 +ResERR_TriggerPending = -18 +ResERR_TriggerWaiting = -19 +ResERR_NoLegitimation = -20 +ResERR_NoTCPLegitimation = -21 +ResERR_NotInPulseMode = -22 +ResERR_OnlyInPulseMode = -23 +ResERR_NotInSwitchMode = -24 +ResERR_OnlyInSwitchMode = -25 +ResERR_TCPErr = -26 +ResERR_StringTooLong = -29 +ResERR_InterruptedByUser = -30 + +# Mode Constants for Callback-Export and WaitForWLMEvent-function cmiResultMode = 1 cmiRange = 2 cmiPulse = 3 @@ -122,6 +114,7 @@ class ResultError(Enum): cmiVersion0 = cmiVersion cmiVersion1 = 96 cmiPulseDelay = 99 +cmiPulseIntegration = cmiPulseDelay cmiDLLAttach = 121 cmiSwitcherSignal = 123 cmiSwitcherMode = 124 @@ -276,6 +269,7 @@ class ResultError(Enum): cmiTriggerState = 1497 cmiDeviceAttach = 1501 cmiDeviceDetach = 1502 +cmiTimePerMeasurement = 1514 cmiAutoExpoMin = 1517 cmiAutoExpoMax = 1518 cmiAutoExpoStepUp = 1519 @@ -286,32 +280,130 @@ class ResultError(Enum): cmiAveragingCount = 1524 cmiAveragingMode = 1525 cmiAveragingType = 1526 - -# Helper dictionary for multichannel wavelength readout -cmi_wavelength_n = { - cmiWavelength1: 1, - cmiWavelength2: 2, - cmiWavelength3: 3, - cmiWavelength4: 4, - cmiWavelength5: 5, - cmiWavelength6: 6, - cmiWavelength7: 7, - cmiWavelength8: 8, - cmiWavelength9: 9, - cmiWavelength10: 10, - cmiWavelength11: 11, - cmiWavelength12: 12, - cmiWavelength13: 13, - cmiWavelength14: 14, - cmiWavelength15: 15, - cmiWavelength16: 16, - cmiWavelength17: 17 -} - -## Index constants for Get- and SetExtraSetting +cmiNowTick_d = 1527 +cmiAirMode = 1532 +cmiAirTemperature = 1534 +cmiAirPressure = 1535 +cmiAirHumidity = 1536 +cmiAirCO2 = 1651 +cmiSubSnapshotID = 1539 +cmiInternalTriggerRate = 1540 +cmiGain11 = 1541 +cmiGain12 = 1542 +cmiGain13 = 1543 +cmiGain14 = 1544 +cmiGain15 = 1545 +cmiGain16 = 1546 +cmiGain17 = 1547 +cmiGain18 = 1548 +cmiGain19 = 1549 +cmiGain110 = 1550 +cmiGain111 = 1551 +cmiGain112 = 1552 +cmiGain113 = 1553 +cmiGain114 = 1554 +cmiGain115 = 1555 +cmiGain116 = 1556 +cmiGain117 = 1557 +cmiGain21 = 1558 +cmiGain22 = 1559 +cmiGain23 = 1560 +cmiGain24 = 1561 +cmiGain25 = 1562 +cmiGain26 = 1563 +cmiGain27 = 1564 +cmiGain28 = 1565 +cmiGain29 = 1566 +cmiGain210 = 1567 +cmiGain211 = 1568 +cmiGain212 = 1569 +cmiGain213 = 1570 +cmiGain214 = 1571 +cmiGain215 = 1572 +cmiGain216 = 1573 +cmiGain217 = 1574 +cmiGain31 = 1575 +cmiGain32 = 1576 +cmiGain33 = 1577 +cmiGain34 = 1578 +cmiGain35 = 1579 +cmiGain36 = 1580 +cmiGain37 = 1581 +cmiGain38 = 1582 +cmiGain39 = 1583 +cmiGain310 = 1584 +cmiGain311 = 1585 +cmiGain312 = 1586 +cmiGain313 = 1587 +cmiGain314 = 1588 +cmiGain315 = 1589 +cmiGain316 = 1590 +cmiGain317 = 1591 +cmiGain41 = 1592 +cmiGain42 = 1593 +cmiGain43 = 1594 +cmiGain44 = 1595 +cmiGain45 = 1596 +cmiGain46 = 1597 +cmiGain47 = 1598 +cmiGain48 = 1599 +cmiGain49 = 1600 +cmiGain410 = 1601 +cmiGain411 = 1602 +cmiGain412 = 1603 +cmiGain413 = 1604 +cmiGain414 = 1605 +cmiGain415 = 1606 +cmiGain416 = 1607 +cmiGain417 = 1608 +cmiMultimodeLevel1 = 1609 +cmiMultimodeLevel2 = 1610 +cmiMultimodeLevel3 = 1611 +cmiMultimodeLevel4 = 1612 +cmiMultimodeLevel5 = 1613 +cmiMultimodeLevel6 = 1614 +cmiMultimodeLevel7 = 1615 +cmiMultimodeLevel8 = 1616 +cmiMultimodeLevel9 = 1617 +cmiMultimodeLevel10 = 1618 +cmiMultimodeLevel11 = 1619 +cmiMultimodeLevel12 = 1620 +cmiMultimodeLevel13 = 1621 +cmiMultimodeLevel14 = 1622 +cmiMultimodeLevel15 = 1623 +cmiMultimodeLevel16 = 1624 +cmiMultimodeLevel17 = 1625 +cmiFastBasedLinewidthAnalysis = 1630 +cmiMultimodeCount1 = 1633 +cmiMultimodeCount2 = 1634 +cmiMultimodeCount3 = 1635 +cmiMultimodeCount4 = 1636 +cmiMultimodeCount5 = 1637 +cmiMultimodeCount6 = 1638 +cmiMultimodeCount7 = 1639 +cmiMultimodeCount8 = 1640 +cmiMultimodeCount9 = 1641 +cmiMultimodeCount10 = 1642 +cmiMultimodeCount11 = 1643 +cmiMultimodeCount12 = 1644 +cmiMultimodeCount13 = 1645 +cmiMultimodeCount14 = 1646 +cmiMultimodeCount15 = 1647 +cmiMultimodeCount16 = 1648 +cmiMultimodeCount17 = 1649 +cmiPowerCalibration = 1652 +cmiStartPowerCalibration = 1653 +cmiEndPowerCalibration = 1654 + +# Index constants for Get- and SetExtraSetting cesCalculateLive = 4501 +cesTryCalcOnAllErrors = 4530 +cesTryCalcOnUnderexposure = 4531 +cesCalcUnderexposureMinLevel = 4561 +cesTrancheAverage = 4600 +cesTTLBatchCount = 4601 -## WLM Control Mode Constants +# WLM Control Mode Constants cCtrlWLMShow = 1 cCtrlWLMHide = 2 cCtrlWLMExit = 3 @@ -322,13 +414,13 @@ class ResultError(Enum): cCtrlWLMSilent = 0x0040 cCtrlWLMStartDelay = 0x0080 -## Operation Mode Constants (for "Operation" and "GetOperationState" functions) +# Operation Mode Constants (for "Operation" and "GetOperationState" functions) cStop = 0 cAdjustment = 1 cMeasurement = 2 -## Base Operation Constants (To be used exclusively, only one of this list at a time, -## but still can be combined with "Measurement Action Addition Constants". See below.) +# Base Operation Constants (To be used exclusively, only one of this list at a time, +# but still can be combined with "Measurement Action Addition Constants". See below.) cCtrlStopAll = cStop cCtrlStartAdjustment = cAdjustment cCtrlStartMeasurement = cMeasurement @@ -337,15 +429,15 @@ class ResultError(Enum): cCtrlStoreArray = 0x0010 cCtrlLoadArray = 0x0020 -## Additional Operation Flag Constants (combine with "Base Operation Constants" above.) +# Additional Operation Flag Constants (combine with "Base Operation Constants" above.) cCtrlDontOverwrite = 0x0000 -cCtrlOverwrite = 0x1000 ## don't combine with cCtrlFileDialog +cCtrlOverwrite = 0x1000 # don't combine with cCtrlFileDialog cCtrlFileGiven = 0x0000 -cCtrlFileDialog = 0x2000 ## don't combine with cCtrlOverwrite and cCtrlFileASCII -cCtrlFileBinary = 0x0000 ## *.smr, *.ltr -cCtrlFileASCII = 0x4000 ## *.smx, *.ltx, don't combine with cCtrlFileDialog +cCtrlFileDialog = 0x2000 # don't combine with cCtrlOverwrite and cCtrlFileASCII +cCtrlFileBinary = 0x0000 # *.smr, *.ltr +cCtrlFileASCII = 0x4000 # *.smx, *.ltx, don't combine with cCtrlFileDialog -## Measurement Control Mode Constants +# Measurement Control Mode Constants cCtrlMeasDelayRemove = 0 cCtrlMeasDelayGenerally = 1 cCtrlMeasDelayOnce = 2 @@ -354,20 +446,20 @@ class ResultError(Enum): cCtrlMeasDelayIdleEach = 5 cCtrlMeasDelayDefault = 6 -## Measurement Triggering Action Constants +# Measurement Triggering Action Constants cCtrlMeasurementContinue = 0 cCtrlMeasurementInterrupt = 1 cCtrlMeasurementTriggerPoll = 2 cCtrlMeasurementTriggerSuccess = 3 cCtrlMeasurementEx = 0x0100 -## ExposureRange Constants +# ExposureRange Constants cExpoMin = 0 cExpoMax = 1 cExpo2Min = 2 cExpo2Max = 3 -## Amplitude Constants +# Amplitude Constants cMin1 = 0 cMin2 = 1 cMax1 = 2 @@ -375,7 +467,7 @@ class ResultError(Enum): cAvg1 = 4 cAvg2 = 5 -## Measurement Range Constants +# Measurement Range Constants cRange_250_410 = 4 cRange_250_425 = 0 cRange_300_410 = 3 @@ -387,41 +479,48 @@ class ResultError(Enum): cRange_1100_1700 = 7 cRange_1100_1800 = cRange_1100_1700 -## Measurement Range Model Constants +# Measurement Range Model Constants cRangeModelOld = 65535 cRangeModelByOrder = 65534 cRangeModelByWavelength = 65533 -## Unit Constants for Get-/SetResultMode, GetLinewidth, Convert... and Calibration +# Unit Constants for Get-/SetResultMode, GetLinewidth, Convert... and Calibration cReturnWavelengthVac = 0 cReturnWavelengthAir = 1 cReturnFrequency = 2 cReturnWavenumber = 3 cReturnPhotonEnergy = 4 -## Power Unit Constants +# Power Unit Constants cPower_muW = 0 -cPower_dBm = 1 +cPower_mW = 1 +cPower_dBm = 2 -## Source Type Constants for Calibration +# Source Type Constants for Calibration cHeNe633 = 0 cHeNe1152 = 0 cNeL = 1 cOther = 2 cFreeHeNe = 3 +cSLR1530 = 5 -## Unit Constants for Autocalibration +# Unit Constants for Autocalibration cACOnceOnStart = 0 cACMeasurements = 1 cACDays = 2 cACHours = 3 cACMinutes = 4 -## ExposureRange Constants +# Options flags for PowerCalibration +cIntSensor1 = 0x00000000 +cIntSensor2 = 0x00000001 +cTryWOStoreDark = 0x00000010 + +# ExposureRange Constants cGetSync = 1 cSetSync = 2 -## Pattern- and Analysis Constants +# Pattern- and Analysis Constants cPatternDisable = 0 cPatternEnable = 1 cAnalysisDisable = cPatternDisable @@ -436,7 +535,7 @@ class ResultError(Enum): cSignalAnalysisX = cSignalAnalysis cSignalAnalysisY = cSignalAnalysis + 1 -## State constants used with AutoExposureSetting functions +# State constants used with AutoExposureSetting functions cJustStepDown = 0 cRestartAtMinimum = 1 cJustStepUp = 0 @@ -444,43 +543,83 @@ class ResultError(Enum): cConsiderFeedback = 1 cDontConsiderFeedback = 0 -## State constants used with AveragingSetting functions +# Options identifiers used with GetOptionInfo +cInfoSwitch = 1 +cInfoSwitchChannelsCount = 2 +cInfoIntNeonLamp = 11 +cInfo2ndExternalPort = 13 +cInfoPID = 21 +cInfoPIDPortsCount = 22 +cInfoPIDPortType = 23 +cInfoPIDPortRes = 24 +cInfoPIDPortUMin = 25 +cInfoPIDPortUMax = 26 + +# PID type constants +cInfoPIDPortTypeInt = 1 +cInfoPIDPortTypeExt = 2 +cInfoPIDPortTypeDigi = 3 + +# State constants used with AveragingSetting functions cAvrgFloating = 1 cAvrgSucceeding = 2 cAvrgSimple = 0 cAvrgPattern = 1 - -class GetFrequencyError(Enum): - """ Return error values of GetFrequency, GetWavelength and GetWLMVersion - """ - ErrNoValue = 0 - ErrNoSignal = -1 - ErrBadSignal = -2 - ErrLowSignal = -3 - ErrBigSignal = -4 - ErrWlmMissing = -5 - ErrNotAvailable = -6 - InfNothingChanged = -7 - ErrNoPulse = -8 - ErrChannelNotAvailable = -10 - ErrDiv0 = -13 - ErrOutOfRange = -14 - ErrUnitNotAvailable_ErrMaxErr = -15 - -## Return errorvalues of GetTemperature and GetPressure +# Return errorvalues of GetFrequency, GetWavelength, GetWLMVersion, GetOptionInfo and GetResultInfo +ErrNoError = 2 # only with GetResultInfo, other functions return the measured value in this case +ErrNoValue = 0 +ErrNoSignal = -1 +ErrBadSignal = -2 +ErrLowSignal = -3 +ErrBigSignal = -4 +ErrWlmMissing = -5 +ErrNotAvailable = -6 +InfNothingChanged = -7 +ErrNoPulse = -8 +ErrChannelNotAvailable = -10 +ErrDiv0 = -13 +ErrOutOfRange = -14 +ErrUnitNotAvailable = -15 +ErrWOCalc = -16 +ErrTCPErr = -26 +ErrParameterOutOfRange = -28 +ErrStringTooLong = -29 +ErrInterruptedByUser = -30 +ErrInfoAlreadyFetched = -31 +ErrNoValueYet = -32 +ErrNoNewValueYet = -33 +ErrMaxErr = ErrNoNewValueYet + +# Return errorvalues of GetTemperature and GetPressure ErrTemperature = -1000 -ErrTempNotMeasured = ErrTemperature + GetFrequencyError.ErrNoValue.value -ErrTempNotAvailable = ErrTemperature + GetFrequencyError.ErrNotAvailable.value -ErrTempWlmMissing = ErrTemperature + GetFrequencyError.ErrWlmMissing.value - -## Return errorvalues of GetDistance -## real errorvalues are ErrDistance combined with those of GetWavelength +ErrTempNotMeasured = ErrTemperature + ErrNoValue +ErrTempNotAvailable = ErrTemperature + ErrNotAvailable +ErrTempWlmMissing = ErrTemperature + ErrWlmMissing + +# Return errorvalues of GetGain +ErrGain = -1000 +ErrGainNotAvailable = ErrGain + ErrNotAvailable +ErrGainWlmMissing = ErrGain + ErrWlmMissing +ErrGainChannelNotAvailable = ErrGain + ErrChannelNotAvailable +ErrGainOutOfRange = ErrGain + ErrOutOfRange +ErrGainParameterOutOfRange = ErrGain + ErrParameterOutOfRange + +# Return errorvalues of GetMultimodeInfo +ErrMMI = -1000 +ErrMMINotAvailable = ErrMMI + ErrNotAvailable +ErrMMIWlmMissing = ErrMMI + ErrWlmMissing +ErrMMIChannelNotAvailable = ErrMMI + ErrChannelNotAvailable +ErrMMIOutOfRange = ErrMMI + ErrOutOfRange +ErrMMIParameterOutOfRange = ErrMMI + ErrParameterOutOfRange + +# Return errorvalues of GetDistance +# real errorvalues are ErrDistance combined with those of GetWavelength ErrDistance = -1000000000 -ErrDistanceNotAvailable = ErrDistance + GetFrequencyError.ErrNotAvailable.value -ErrDistanceWlmMissing = ErrDistance + GetFrequencyError.ErrWlmMissing.value +ErrDistanceNotAvailable = ErrDistance + ErrNotAvailable +ErrDistanceWlmMissing = ErrDistance + ErrWlmMissing -## Return flags of ControlWLMEx in combination with Show or Hide, Wait and Res = 1 +# Return flags of ControlWLMEx in combination with Show or Hide, Wait and Res = 1 flServerStarted = 0x00000001 flErrDeviceNotFound = 0x00000002 flErrDriverError = 0x00000004 @@ -507,10 +646,13 @@ class GetFrequencyError(Enum): flDeviceStampNewer = 0x01000000 flFileStampNewer = 0x02000000 -## Return file info flags of SetOperationFile -flFileInfoDoesntExist = 0x0000 -flFileInfoExists = 0x0001 -flFileInfoCantWrite = 0x0002 -flFileInfoCantRead = 0x0004 -flFileInfoInvalidName = 0x0008 +# Return file info flags of SetOperationFile +flFileInfoDoesntExist = 0x00000000 +flFileInfoExists = 0x00000001 +flFileInfoCantWrite = 0x00000002 +flFileInfoCantRead = 0x00000004 +flFileInfoInvalidName = 0x00000008 cFileParameterError = -1 + + +# *** end of wlmData.h diff --git a/src/qudi/hardware/wavemeter/wlmData.py b/src/qudi/hardware/wavemeter/wlmData.py new file mode 100644 index 0000000..d1597e1 --- /dev/null +++ b/src/qudi/hardware/wavemeter/wlmData.py @@ -0,0 +1,588 @@ +""" +wlmData API function bindings generated from wlmData.h on 2024-11-12 +""" +# pylint: disable=line-too-long + +import ctypes +import platform + +_FUNCTYPE = ctypes.WINFUNCTYPE if platform.system() == 'Windows' else ctypes.CFUNCTYPE + +CALLBACK_TYPE = _FUNCTYPE(None, ctypes.c_int32, ctypes.c_int32, ctypes.c_double) +CALLBACK_EX_TYPE = _FUNCTYPE(None, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_double, + ctypes.c_int32) + +_PROTOTYPES = { +# *********** Functions for general usage **************************** + # intptr_t Instantiate(int32_t RFC, int32_t Mode, intptr_t P1, int32_t P2) + 'Instantiate': (ctypes.c_void_p, (ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p, ctypes.c_int32)), + + + # void CallbackProc(int32_t Mode, int32_t IntVal, double DblVal) + + # void CallbackProcEx(int32_t Ver, int32_t Mode, int32_t IntVal, double DblVal, int32_t Res1) + + # int32_t WaitForWLMEvent(int32_t* Mode, int32_t* IntVal, double* DblVal) + 'WaitForWLMEvent': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double))), + + # int32_t WaitForWLMEventEx(int32_t* Ver, int32_t* Mode, int32_t* IntVal, double* DblVal, int32_t* Res1) + 'WaitForWLMEventEx': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_int32))), + + # int32_t WaitForNextWLMEvent(int32_t* Mode, int32_t* IntVal, double* DblVal) + 'WaitForNextWLMEvent': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double))), + + # int32_t WaitForNextWLMEventEx(int32_t* Ver, int32_t* Mode, int32_t* IntVal, double* DblVal, int32_t* Res1) + 'WaitForNextWLMEventEx': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_int32))), + + # void ClearWLMEvents(void) + 'ClearWLMEvents': (None, ()), + + + # int32_t ControlWLM(int32_t Action, intptr_t App, int32_t Ver) + 'ControlWLM': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_void_p, ctypes.c_int32)), + + # int32_t ControlWLMEx(int32_t Action, intptr_t App, int32_t Ver, int32_t Delay, int32_t Res) + 'ControlWLMEx': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_void_p, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int64_t SynchroniseWLM(int32_t Mode, int64_t TS) + 'SynchroniseWLM': (ctypes.c_int64, (ctypes.c_int32, ctypes.c_int64)), + + # int32_t SetMeasurementDelayMethod(int32_t Mode, int32_t Delay) + 'SetMeasurementDelayMethod': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32)), + + # int32_t SetWLMPriority(int32_t PPC, int32_t Res1, int32_t Res2) + 'SetWLMPriority': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t PresetWLMIndex(int32_t Ver) + 'PresetWLMIndex': (ctypes.c_int32, (ctypes.c_int32,)), + + + # int32_t GetWLMVersion(int32_t Ver) + 'GetWLMVersion': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetWLMIndex(int32_t Ver) + 'GetWLMIndex': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetWLMCount(int32_t V) + 'GetWLMCount': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetOptionInfo(int32_t Index, int32_t Detail, int64_t* I64Val, double* DblVal, char* sVal) + 'GetOptionInfo': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_int64), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_char))), + + +# *********** General Get... & Set...-functions ********************** + # double GetWavelength(double WL) + 'GetWavelength': (ctypes.c_double, (ctypes.c_double,)), + + # double GetWavelength2(double WL2) + 'GetWavelength2': (ctypes.c_double, (ctypes.c_double,)), + + # double GetWavelengthNum(int32_t num, double WL) + 'GetWavelengthNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetCalWavelength(int32_t ba, double WL) + 'GetCalWavelength': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetCalibrationEffect(double CE) + 'GetCalibrationEffect': (ctypes.c_double, (ctypes.c_double,)), + + # double GetFrequency(double F) + 'GetFrequency': (ctypes.c_double, (ctypes.c_double,)), + + # double GetFrequency2(double F2) + 'GetFrequency2': (ctypes.c_double, (ctypes.c_double,)), + + # double GetFrequencyNum(int32_t num, double F) + 'GetFrequencyNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetLinewidth(int32_t Index, double LW) + 'GetLinewidth': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetLinewidthNum(int32_t num, double LW) + 'GetLinewidthNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetDistance(double D) + 'GetDistance': (ctypes.c_double, (ctypes.c_double,)), + + # double GetAnalogIn(double AI) + 'GetAnalogIn': (ctypes.c_double, (ctypes.c_double,)), + + # double GetMultimodeInfo(int32_t num, int32_t type, int32_t mode, double* Val) + 'GetMultimodeInfo': (ctypes.c_double, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_double))), + + # int32_t GetResultInfo(int32_t num, int32_t info) + 'GetResultInfo': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32)), + + # double GetMeasurementUncertainty(int32_t Index, int32_t num, double MU) + 'GetMeasurementUncertainty': (ctypes.c_double, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + # double GetTemperature(double T) + 'GetTemperature': (ctypes.c_double, (ctypes.c_double,)), + + # int32_t SetTemperature(double T) + 'SetTemperature': (ctypes.c_int32, (ctypes.c_double,)), + + # double GetPressure(double P) + 'GetPressure': (ctypes.c_double, (ctypes.c_double,)), + + # int32_t SetPressure(int32_t Mode, double P) + 'SetPressure': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_double)), + + # int32_t GetAirParameters(int32_t Mode, int32_t* State, double* Val) + 'GetAirParameters': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double))), + + # int32_t SetAirParameters(int32_t Mode, int32_t State, double Val) + 'SetAirParameters': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + # double GetExternalInput(int32_t Index, double I) + 'GetExternalInput': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # int32_t SetExternalInput(int32_t Index, double I) + 'SetExternalInput': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_double)), + + # int32_t GetExtraSetting(int32_t Index, int32_t* lGet, double* dGet, char* sGet) + 'GetExtraSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_char))), + + # int32_t SetExtraSetting(int32_t Index, int32_t lSet, double dSet, char* sSet) + 'SetExtraSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double, ctypes.POINTER(ctypes.c_char))), + + + # uint16_t GetExposure(uint16_t E) + 'GetExposure': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetExposure(uint16_t E) + 'SetExposure': (ctypes.c_int32, (ctypes.c_uint16,)), + + # uint16_t GetExposure2(uint16_t E2) + 'GetExposure2': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetExposure2(uint16_t E2) + 'SetExposure2': (ctypes.c_int32, (ctypes.c_uint16,)), + + # int32_t GetExposureNum(int32_t num, int32_t arr, int32_t E) + 'GetExposureNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t SetExposureNum(int32_t num, int32_t arr, int32_t E) + 'SetExposureNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # double GetExposureNumEx(int32_t num, int32_t arr, double E) + 'GetExposureNumEx': (ctypes.c_double, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + # int32_t SetExposureNumEx(int32_t num, int32_t arr, double E) + 'SetExposureNumEx': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + # bool GetExposureMode(bool EM) + 'GetExposureMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetExposureMode(bool EM) + 'SetExposureMode': (ctypes.c_int32, (ctypes.c_bool,)), + + # int32_t GetExposureModeNum(int32_t num, bool EM) + 'GetExposureModeNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_bool)), + + # int32_t SetExposureModeNum(int32_t num, bool EM) + 'SetExposureModeNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_bool)), + + # int32_t GetExposureRange(int32_t ER) + 'GetExposureRange': (ctypes.c_int32, (ctypes.c_int32,)), + + # double GetExposureRangeEx(int32_t ER) + 'GetExposureRangeEx': (ctypes.c_double, (ctypes.c_int32,)), + + # int32_t GetAutoExposureSetting(int32_t num, int32_t AES, int32_t* iVal, double* dVal) + 'GetAutoExposureSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double))), + + # int32_t SetAutoExposureSetting(int32_t num, int32_t AES, int32_t iVal, double dVal) + 'SetAutoExposureSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + + # uint16_t GetResultMode(uint16_t RM) + 'GetResultMode': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetResultMode(uint16_t RM) + 'SetResultMode': (ctypes.c_int32, (ctypes.c_uint16,)), + + # uint16_t GetRange(uint16_t R) + 'GetRange': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetRange(uint16_t R) + 'SetRange': (ctypes.c_int32, (ctypes.c_uint16,)), + + # uint16_t GetPulseMode(uint16_t PM) + 'GetPulseMode': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetPulseMode(uint16_t PM) + 'SetPulseMode': (ctypes.c_int32, (ctypes.c_uint16,)), + + # int32_t GetPulseDelay(int32_t PD) + 'GetPulseDelay': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetPulseDelay(int32_t PD) + 'SetPulseDelay': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetPulseIntegration(int32_t PI) + 'GetPulseIntegration': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetPulseIntegration(int32_t PI) + 'SetPulseIntegration': (ctypes.c_int32, (ctypes.c_int32,)), + + # uint16_t GetWideMode(uint16_t WM) + 'GetWideMode': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetWideMode(uint16_t WM) + 'SetWideMode': (ctypes.c_int32, (ctypes.c_uint16,)), + + + # int32_t GetDisplayMode(int32_t DM) + 'GetDisplayMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetDisplayMode(int32_t DM) + 'SetDisplayMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # bool GetFastMode(bool FM) + 'GetFastMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetFastMode(bool FM) + 'SetFastMode': (ctypes.c_int32, (ctypes.c_bool,)), + + + # bool GetLinewidthMode(bool LM) + 'GetLinewidthMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetLinewidthMode(bool LM) + 'SetLinewidthMode': (ctypes.c_int32, (ctypes.c_bool,)), + + + # bool GetDistanceMode(bool DM) + 'GetDistanceMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetDistanceMode(bool DM) + 'SetDistanceMode': (ctypes.c_int32, (ctypes.c_bool,)), + + + # int32_t GetSwitcherMode(int32_t SM) + 'GetSwitcherMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetSwitcherMode(int32_t SM) + 'SetSwitcherMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetSwitcherChannel(int32_t CH) + 'GetSwitcherChannel': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetSwitcherChannel(int32_t CH) + 'SetSwitcherChannel': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetSwitcherSignalStates(int32_t Signal, int32_t* Use, int32_t* Show) + 'GetSwitcherSignalStates': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32))), + + # int32_t SetSwitcherSignalStates(int32_t Signal, int32_t Use, int32_t Show) + 'SetSwitcherSignalStates': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t SetSwitcherSignal(int32_t Signal, int32_t Use, int32_t Show) + 'SetSwitcherSignal': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + + # int32_t GetAutoCalMode(int32_t ACM) + 'GetAutoCalMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetAutoCalMode(int32_t ACM) + 'SetAutoCalMode': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetAutoCalSetting(int32_t ACS, int32_t* val, int32_t Res1, int32_t* Res2) + 'GetAutoCalSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.c_int32, ctypes.POINTER(ctypes.c_int32))), + + # int32_t SetAutoCalSetting(int32_t ACS, int32_t val, int32_t Res1, int32_t Res2) + 'SetAutoCalSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + + # int32_t GetActiveChannel(int32_t Mode, int32_t* Port, int32_t Res1) + 'GetActiveChannel': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.c_int32)), + + # int32_t SetActiveChannel(int32_t Mode, int32_t Port, int32_t CH, int32_t Res1) + 'SetActiveChannel': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t GetChannelsCount(int32_t C) + 'GetChannelsCount': (ctypes.c_int32, (ctypes.c_int32,)), + + + # uint16_t GetOperationState(uint16_t OS) + 'GetOperationState': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t Operation(uint16_t Op) + 'Operation': (ctypes.c_int32, (ctypes.c_uint16,)), + + # int32_t SetOperationFile(char* lpFile) + 'SetOperationFile': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_char),)), + + # int32_t Calibration(int32_t Type, int32_t Unit, double Value, int32_t Channel) + 'Calibration': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_double, ctypes.c_int32)), + + # int32_t PowerCalibration(int32_t Unit, double Value, int32_t Channel, int32_t Options, int32_t Res) + 'PowerCalibration': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_double, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t RaiseMeasurementEvent(int32_t Mode) + 'RaiseMeasurementEvent': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t TriggerMeasurement(int32_t Action) + 'TriggerMeasurement': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetTriggerState(int32_t TS) + 'GetTriggerState': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetInterval(int32_t I) + 'GetInterval': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetInterval(int32_t I) + 'SetInterval': (ctypes.c_int32, (ctypes.c_int32,)), + + # bool GetIntervalMode(bool IM) + 'GetIntervalMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetIntervalMode(bool IM) + 'SetIntervalMode': (ctypes.c_int32, (ctypes.c_bool,)), + + # double GetInternalTriggerRate(double TR) + 'GetInternalTriggerRate': (ctypes.c_double, (ctypes.c_double,)), + + # int32_t SetInternalTriggerRate(double TR) + 'SetInternalTriggerRate': (ctypes.c_int32, (ctypes.c_double,)), + + # int32_t GetBackground(int32_t BG) + 'GetBackground': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetBackground(int32_t BG) + 'SetBackground': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetAveragingSettingNum(int32_t num, int32_t AS, int32_t Value) + 'GetAveragingSettingNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # int32_t SetAveragingSettingNum(int32_t num, int32_t AS, int32_t Value) + 'SetAveragingSettingNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + + # bool GetLinkState(bool LS) + 'GetLinkState': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetLinkState(bool LS) + 'SetLinkState': (ctypes.c_int32, (ctypes.c_bool,)), + + # void LinkSettingsDlg(void) + 'LinkSettingsDlg': (None, ()), + + + # int32_t GetPatternItemSize(int32_t Index) + 'GetPatternItemSize': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetPatternItemCount(int32_t Index) + 'GetPatternItemCount': (ctypes.c_int32, (ctypes.c_int32,)), + + # uintptr_t GetPattern(int32_t Index) + 'GetPattern': (ctypes.c_void_p, (ctypes.c_int32,)), + + # uintptr_t GetPatternNum(int32_t Chn, int32_t Index) + 'GetPatternNum': (ctypes.c_void_p, (ctypes.c_int32, ctypes.c_int32)), + + # int32_t GetPatternData(int32_t Index, uintptr_t PArray) + 'GetPatternData': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_void_p)), + + # int32_t GetPatternDataNum(int32_t Chn, int32_t Index, uintptr_t PArray) + 'GetPatternDataNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p)), + + # int32_t SetPattern(int32_t Index, int32_t iEnable) + 'SetPattern': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32)), + + # int32_t SetPatternData(int32_t Index, uintptr_t PArray) + 'SetPatternData': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_void_p)), + + + # bool GetAnalysisMode(bool AM) + 'GetAnalysisMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetAnalysisMode(bool AM) + 'SetAnalysisMode': (ctypes.c_int32, (ctypes.c_bool,)), + + # int32_t GetAnalysisItemSize(int32_t Index) + 'GetAnalysisItemSize': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetAnalysisItemCount(int32_t Index) + 'GetAnalysisItemCount': (ctypes.c_int32, (ctypes.c_int32,)), + + # uintptr_t GetAnalysis(int32_t Index) + 'GetAnalysis': (ctypes.c_void_p, (ctypes.c_int32,)), + + # int32_t GetAnalysisData(int32_t Index, uintptr_t PArray) + 'GetAnalysisData': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_void_p)), + + # int32_t SetAnalysis(int32_t Index, int32_t iEnable) + 'SetAnalysis': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32)), + + + # int32_t GetMinPeak(int32_t M1) + 'GetMinPeak': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetMinPeak2(int32_t M2) + 'GetMinPeak2': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetMaxPeak(int32_t X1) + 'GetMaxPeak': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetMaxPeak2(int32_t X2) + 'GetMaxPeak2': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetAvgPeak(int32_t A1) + 'GetAvgPeak': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t GetAvgPeak2(int32_t A2) + 'GetAvgPeak2': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetAvgPeak(int32_t PA) + 'SetAvgPeak': (ctypes.c_int32, (ctypes.c_int32,)), + + + # int32_t GetAmplitudeNum(int32_t num, int32_t Index, int32_t A) + 'GetAmplitudeNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + # double GetIntensityNum(int32_t num, double I) + 'GetIntensityNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # double GetPowerNum(int32_t num, double P) + 'GetPowerNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + + # uint16_t GetDelay(uint16_t D) + 'GetDelay': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetDelay(uint16_t D) + 'SetDelay': (ctypes.c_int32, (ctypes.c_uint16,)), + + # uint16_t GetShift(uint16_t S) + 'GetShift': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetShift(uint16_t S) + 'SetShift': (ctypes.c_int32, (ctypes.c_uint16,)), + + # uint16_t GetShift2(uint16_t S2) + 'GetShift2': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetShift2(uint16_t S2) + 'SetShift2': (ctypes.c_int32, (ctypes.c_uint16,)), + + # double GetGain(int32_t num, int32_t index, int32_t mode, double* Gain) + 'GetGain': (ctypes.c_double, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_double))), + + # int32_t SetGain(int32_t num, int32_t index, int32_t mode, double Gain) + 'SetGain': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + +# *********** Deviation (Laser Control) and PID-functions ************ + # bool GetDeviationMode(bool DM) + 'GetDeviationMode': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetDeviationMode(bool DM) + 'SetDeviationMode': (ctypes.c_int32, (ctypes.c_bool,)), + + # double GetDeviationReference(double DR) + 'GetDeviationReference': (ctypes.c_double, (ctypes.c_double,)), + + # int32_t SetDeviationReference(double DR) + 'SetDeviationReference': (ctypes.c_int32, (ctypes.c_double,)), + + # int32_t GetDeviationSensitivity(int32_t DS) + 'GetDeviationSensitivity': (ctypes.c_int32, (ctypes.c_int32,)), + + # int32_t SetDeviationSensitivity(int32_t DS) + 'SetDeviationSensitivity': (ctypes.c_int32, (ctypes.c_int32,)), + + # double GetDeviationSignal(double DS) + 'GetDeviationSignal': (ctypes.c_double, (ctypes.c_double,)), + + # double GetDeviationSignalNum(int32_t Port, double DS) + 'GetDeviationSignalNum': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + # int32_t SetDeviationSignal(double DS) + 'SetDeviationSignal': (ctypes.c_int32, (ctypes.c_double,)), + + # int32_t SetDeviationSignalNum(int32_t Port, double DS) + 'SetDeviationSignalNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_double)), + + # double RaiseDeviationSignal(int32_t iType, double dSignal) + 'RaiseDeviationSignal': (ctypes.c_double, (ctypes.c_int32, ctypes.c_double)), + + + # int32_t GetPIDCourse(char* PIDC) + 'GetPIDCourse': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_char),)), + + # int32_t SetPIDCourse(char* PIDC) + 'SetPIDCourse': (ctypes.c_int32, (ctypes.POINTER(ctypes.c_char),)), + + # int32_t GetPIDCourseNum(int32_t Port, char* PIDC) + 'GetPIDCourseNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_char))), + + # int32_t SetPIDCourseNum(int32_t Port, char* PIDC) + 'SetPIDCourseNum': (ctypes.c_int32, (ctypes.c_int32, ctypes.POINTER(ctypes.c_char))), + + # int32_t GetPIDSetting(int32_t PS, int32_t Port, int32_t* iSet, double* dSet) + 'GetPIDSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double))), + + # int32_t SetPIDSetting(int32_t PS, int32_t Port, int32_t iSet, double dSet) + 'SetPIDSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_double)), + + # int32_t GetLaserControlSetting(int32_t PS, int32_t Port, int32_t* iSet, double* dSet, char* sSet) + 'GetLaserControlSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_char))), + + # int32_t SetLaserControlSetting(int32_t PS, int32_t Port, int32_t iSet, double dSet, char* sSet) + 'SetLaserControlSetting': (ctypes.c_int32, (ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_double, ctypes.POINTER(ctypes.c_char))), + + # int32_t ClearPIDHistory(int32_t Port) + 'ClearPIDHistory': (ctypes.c_int32, (ctypes.c_int32,)), + + +# *********** Other...-functions ************************************* + # double ConvertUnit(double Val, int32_t uFrom, int32_t uTo) + 'ConvertUnit': (ctypes.c_double, (ctypes.c_double, ctypes.c_int32, ctypes.c_int32)), + + # double ConvertDeltaUnit(double Base, double Delta, int32_t uBase, int32_t uFrom, int32_t uTo) + 'ConvertDeltaUnit': (ctypes.c_double, (ctypes.c_double, ctypes.c_double, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32)), + + +# *********** Obsolete...-functions ********************************** + # bool GetReduced(bool R) + 'GetReduced': (ctypes.c_bool, (ctypes.c_bool,)), + + # int32_t SetReduced(bool R) + 'SetReduced': (ctypes.c_int32, (ctypes.c_bool,)), + + # uint16_t GetScale(uint16_t S) + 'GetScale': (ctypes.c_uint16, (ctypes.c_uint16,)), + + # int32_t SetScale(uint16_t S) + 'SetScale': (ctypes.c_int32, (ctypes.c_uint16,)), + + +} + +dll = None # pylint: disable=invalid-name + +def LoadDLL(path = None): # pylint: disable=invalid-name + """Load wlmData library""" + global dll # pylint: disable=global-statement + + if path is None: + if platform.system() == 'Windows': + path = 'wlmData.dll' + elif platform.system() == 'Darwin': + path = 'libwlmData.dylib' + else: + path = 'libwlmData.so' + + dll = ctypes.WinDLL(path) if platform.system() == 'Windows' else ctypes.CDLL(path) + + for name, (restype, argtypes) in _PROTOTYPES.items(): + try: + fnptr = getattr(dll, name) + except AttributeError: + continue + fnptr.argtypes = argtypes + fnptr.restype = restype + + return dll diff --git a/src/qudi/interface/data_instream_interface.py b/src/qudi/interface/data_instream_interface.py deleted file mode 100644 index a1a37c0..0000000 --- a/src/qudi/interface/data_instream_interface.py +++ /dev/null @@ -1,273 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Interface for a generic input stream of data points with fixed sampling rate and data type. - -Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this -distribution and on - -This file is part of qudi. - -Qudi is free software: you can redistribute it and/or modify it under the terms of -the GNU Lesser 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along with qudi. -If not, see . -""" - -__all__ = ['StreamingMode', 'SampleTiming', 'DataInStreamConstraints', 'DataInStreamInterface'] - -import numpy as np -from typing import Union, Type, Iterable, Mapping, Optional, Dict, List, Tuple, Sequence -from enum import Enum -from abc import abstractmethod -from qudi.core.module import Base -from qudi.util.constraints import ScalarConstraint - - -class StreamingMode(Enum): - INVALID = -1 - CONTINUOUS = 0 - FINITE = 1 - - -class SampleTiming(Enum): - INVALID = -1 - CONSTANT = 0 - TIMESTAMP = 1 - RANDOM = 2 - - -class DataInStreamConstraints: - """ Collection of constraints for hardware modules implementing DataInStreamInterface """ - def __init__(self, - channel_units: Mapping[str, str], - sample_timing: Union[SampleTiming, int], - streaming_modes: Iterable[Union[StreamingMode, int]], - data_type: Union[Type[int], Type[float], Type[np.integer], Type[np.floating]], - channel_buffer_size: Optional[ScalarConstraint], - sample_rate: Optional[ScalarConstraint] = None): - if not isinstance(sample_rate, ScalarConstraint) and sample_rate is not None: - raise TypeError( - f'"sample_rate" must be None or' - f'{ScalarConstraint.__module__}.{ScalarConstraint.__qualname__} instance' - ) - if not isinstance(channel_buffer_size, ScalarConstraint): - raise TypeError( - f'"channel_buffer_size" must be ' - f'{ScalarConstraint.__module__}.{ScalarConstraint.__qualname__} instance' - ) - self._channel_units = {**channel_units} - self._sample_timing = SampleTiming(sample_timing) - self._streaming_modes = [StreamingMode(mode) for mode in streaming_modes] - self._data_type = np.dtype(data_type).type - self._channel_buffer_size = channel_buffer_size - if sample_rate is None: - if self._sample_timing != SampleTiming.RANDOM: - raise ValueError('"sample_rate" ScalarConstraint must be provided if ' - '"sample_timing" is not SampleTiming.RANDOM') - self._sample_rate = ScalarConstraint(default=1, bounds=(1, 1), increment=0) - else: - self._sample_rate = sample_rate - - @property - def channel_units(self) -> Dict[str, str]: - return self._channel_units.copy() - - @property - def sample_timing(self) -> SampleTiming: - return self._sample_timing - - @property - def streaming_modes(self) -> List[StreamingMode]: - return self._streaming_modes.copy() - - @property - def data_type(self) -> np.dtype: - return self._data_type - - @property - def sample_rate(self) -> ScalarConstraint: - return self._sample_rate - - @property - def channel_buffer_size(self) -> ScalarConstraint: - return self._channel_buffer_size - - -class DataInStreamInterface(Base): - """ Interface for a generic input stream (finite or infinite) of data points from multiple - channels with common data type. - - A connecting logic module can choose to manage its own buffer array(s) and let the hardware - module read samples directly into the provided arrays for maximum data throughput using - "read_data_into_buffer" and "read_available_data_into_buffer". Alternatively one can call - "read_data" or "read_single_point" which will return sufficiently large data arrays that are - newly allocated each time the method is called (less efficient but no buffer handling needed). - In any case each time a "read_..." method is called, the samples returned are not available - anymore and will be consumed. So multiple logic modules can not read from the same data stream. - - The sample timing can behave according to 3 different modes (Enum). Check constraints to see - which mode is used by the hardware. - - SampleTiming.CONSTANT: The sample rate is deterministic and constant. Each sample in the stream - has a fixed timing determined by the sample rate. - SampleTiming.TIMESTAMP: The sample rate is just a hint for the hardware but can not be - considered constant. The hardware will provide a numpy.float64 timestamp - in seconds from the start of the stream for each sample. This requires - an additional timestamp buffer array in addition to the normal channel - sample buffer. - SampleTiming.RANDOM: The sample rate is just a hint for the hardware but can not be - considered constant. There is no deterministic time correlation between - samples, except that they are acquired one after another. - """ - - @property - @abstractmethod - def constraints(self) -> DataInStreamConstraints: - """ Read-only property returning the constraints on the settings for this data streamer. """ - pass - - @property - @abstractmethod - def available_samples(self) -> int: - """ Read-only property to return the currently available number of samples per channel ready - to read from buffer. - It must be ensured that each channel can provide at least the number of samples returned - by this property. - """ - pass - - @property - @abstractmethod - def sample_rate(self) -> float: - """ Read-only property returning the currently set sample rate in Hz. - For SampleTiming.CONSTANT this is the sample rate of the hardware, for any other timing mode - this property represents only a hint to the actual hardware timebase and can not be - considered accurate. - """ - pass - - @property - @abstractmethod - def channel_buffer_size(self) -> int: - """ Read-only property returning the currently set buffer size in samples per channel. - The total buffer size in bytes can be estimated by: - * * numpy.nbytes[] - - For StreamingMode.FINITE this will also be the total number of samples to acquire per - channel. - """ - pass - - @property - @abstractmethod - def streaming_mode(self) -> StreamingMode: - """ Read-only property returning the currently configured StreamingMode Enum """ - pass - - @property - @abstractmethod - def active_channels(self) -> List[str]: - """ Read-only property returning the currently configured active channel names """ - pass - - @abstractmethod - def configure(self, - active_channels: Sequence[str], - streaming_mode: Union[StreamingMode, int], - channel_buffer_size: int, - sample_rate: float) -> None: - """ Configure a data stream. See read-only properties for information on each parameter. """ - pass - - @abstractmethod - def start_stream(self) -> None: - """ Start the data acquisition/streaming """ - pass - - @abstractmethod - def stop_stream(self) -> None: - """ Stop the data acquisition/streaming """ - pass - - @abstractmethod - def read_data_into_buffer(self, - data_buffer: np.ndarray, - samples_per_channel: int, - timestamp_buffer: Optional[np.ndarray] = None) -> None: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - Samples of all channels are stored interleaved in contiguous memory. - In case of a multidimensional buffer array, this buffer will be flattened before written - into. - The 1D data_buffer can be unraveled into channel and sample indexing with: - - data_buffer.reshape([, ]) - - The data_buffer array must have the same data type as self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array has to be - provided to be filled with timestamps corresponding to the data_buffer array. It must be - able to hold at least items: - - This function is blocking until the required number of samples has been acquired. - """ - pass - - @abstractmethod - def read_available_data_into_buffer(self, - data_buffer: np.ndarray, - timestamp_buffer: Optional[np.ndarray] = None) -> int: - """ Read data from the stream buffer into a 1D numpy array given as parameter. - All samples for each channel are stored in consecutive blocks one after the other. - The number of samples read per channel is returned and can be used to slice out valid data - from the buffer arrays like: - - valid_data = data_buffer[: * ] - valid_timestamps = timestamp_buffer[:] - - See "read_data_into_buffer" documentation for more details. - - This method will read all currently available samples into buffer. If number of available - samples exceeds buffer size, read only as many samples as fit into the buffer. - """ - pass - - @abstractmethod - def read_data(self, - samples_per_channel: Optional[int] = None - ) -> Tuple[np.ndarray, Union[np.ndarray, None]]: - """ Read data from the stream buffer into a 1D numpy array and return it. - All samples for each channel are stored in consecutive blocks one after the other. - The returned data_buffer can be unraveled into channel samples with: - - data_buffer.reshape([, ]) - - The numpy array data type is the one defined in self.constraints.data_type. - - In case of SampleTiming.TIMESTAMP a 1D numpy.float64 timestamp_buffer array will be - returned as well with timestamps corresponding to the data_buffer array. - - If samples_per_channel is omitted all currently available samples are read from buffer. - This method will not return until all requested samples have been read or a timeout occurs. - """ - pass - - @abstractmethod - def read_single_point(self) -> Tuple[np.ndarray, Union[None, np.float64]]: - """ This method will initiate a single sample read on each configured data channel. - In general this sample may not be acquired simultaneous for all channels and timing in - general can not be assured. Us this method if you want to have a non-timing-critical - snapshot of your current data channel input. - May not be available for all devices. - The returned 1D numpy array will contain one sample for each channel. - - In case of SampleTiming.TIMESTAMP a single numpy.float64 timestamp value will be returned - as well. - """ - pass diff --git a/src/qudi/interface/simple_wavemeter_interface.py b/src/qudi/interface/simple_wavemeter_interface.py new file mode 100644 index 0000000..843a15b --- /dev/null +++ b/src/qudi/interface/simple_wavemeter_interface.py @@ -0,0 +1,17 @@ +import numpy as np +from abc import abstractmethod +from qudi.core.module import Base + + +class SimpleWavemeterInterface(Base): + """ This interface is for simple use of a wavemeter. It just gets wavelength for any available channels + + """ + + @abstractmethod + def get_wavelengths(self) -> np.ndarray: + """ Retrieve the current wavelength(s) from the wavemeter + + @return np.ndarray: wavelength in nm per-channel + """ + pass \ No newline at end of file diff --git a/src/qudi/logic/common/data_in_stream_logic.py b/src/qudi/logic/common/data_in_stream_logic.py deleted file mode 100644 index 670517b..0000000 --- a/src/qudi/logic/common/data_in_stream_logic.py +++ /dev/null @@ -1,834 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This file contains the qudi logic to continuously read data from a streaming device as time series. - -Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this -distribution and on - -This file is part of qudi. - -Qudi is free software: you can redistribute it and/or modify it under the terms of -the GNU Lesser 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along with qudi. -If not, see . -""" - -import numpy as np -import datetime as dt -import matplotlib.pyplot as plt -from PySide2 import QtCore -from scipy.signal import decimate -from typing import Union, Optional, Sequence, Iterable, List, Dict, Mapping, Tuple - -from qudi.core.connector import Connector -from qudi.core.statusvariable import StatusVar -from qudi.core.configoption import ConfigOption -from qudi.core.module import LogicBase -from qudi.util.mutex import Mutex -from qudi.util.helpers import is_integer_type -from qudi.util.network import netobtain -from qudi.interface.data_instream_interface import StreamingMode, SampleTiming -from qudi.interface.data_instream_interface import DataInStreamConstraints -from qudi.util.datastorage import TextDataStorage -from qudi.util.units import ScaledFloat - - -class DataInStreamLogic(LogicBase): - """ - This logic module gathers data from a hardware streaming device. - - Example config for copy-paste: - - time_series_reader_logic: - module.Class: 'common.data_in_stream_logic.DataInStreamLogic' - options: - max_frame_rate: 20 # optional (default: 20Hz) - channel_buffer_size: 1048576 # optional (default: 1MSample) - max_raw_data_bytes: 1073741824 # optional (default: 1GB) - connect: - streamer: instream_dummy - """ - # declare signals - sigDataChanged = QtCore.Signal(object, object, object, object) - sigNewRawData = QtCore.Signal(object, object) # raw data samples, timestamp samples (optional) - sigStatusChanged = QtCore.Signal(bool, bool) - sigTraceSettingsChanged = QtCore.Signal(dict) - sigChannelSettingsChanged = QtCore.Signal(list, list) - _sigNextDataFrame = QtCore.Signal() # internal signal - - # declare connectors - _streamer = Connector(name='streamer', interface='DataInStreamInterface') - - # config options - _max_frame_rate = ConfigOption('max_frame_rate', default=20, missing='warn') - _channel_buffer_size = ConfigOption(name='channel_buffer_size', - default=1024**2, - missing='info', - constructor=lambda x: int(round(x))) - _max_raw_data_bytes = ConfigOption(name='max_raw_data_bytes', - default=1024**3, - missing='info', - constructor=lambda x: int(round(x))) - - # status vars - _trace_window_size = StatusVar('trace_window_size', default=6) - _moving_average_width = StatusVar('moving_average_width', default=9) - _oversampling_factor = StatusVar('oversampling_factor', default=1) - _data_rate = StatusVar('data_rate', default=50) - _active_channels = StatusVar('active_channels', default=None) - _averaged_channels = StatusVar('averaged_channels', default=None) - - @_data_rate.representer - def __repr_data_rate(self, value): - return self.data_rate - - @_active_channels.representer - def __repr_active_channels(self, value): - return self.active_channel_names - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # locking for thread safety - self._threadlock = Mutex() - self._samples_per_frame = None - - # Data arrays - self._data_buffer = None - self._times_buffer = None - self._trace_data = None - self._trace_times = None - self._trace_data_averaged = None - self.__moving_filter = None - - # for data recording - self._recorded_raw_data = None - self._recorded_raw_times = None - self._recorded_sample_count = 0 - self._data_recording_active = False - self._record_start_time = None - - # important to know for method of reading the buffer - self._streamer_is_remote = False - - def on_activate(self) -> None: - """ Initialisation performed during activation of the module. """ - # Temp reference to connected hardware module - streamer = self._streamer() - - # check if streamer is a remote module - constraints = streamer.constraints - if type(constraints) != type(netobtain(constraints)): - self._streamer_is_remote = True - self.log.debug('Streamer is a remote module. Do not use a shared buffer.') - - # Flag to stop the loop and process variables - self._recorded_raw_data = None - self._recorded_raw_times = None - self._recorded_sample_count = 0 - self._data_recording_active = False - self._record_start_time = None - - # Check valid StatusVar - # active channels - avail_channels = list(constraints.channel_units) - if self._active_channels is None: - if streamer.active_channels: - self._active_channels = streamer.active_channels.copy() - else: - self._active_channels = avail_channels - self._averaged_channels = self._active_channels - elif any(ch not in avail_channels for ch in self._active_channels): - self.log.warning('Invalid active channels found in StatusVar. StatusVar ignored.') - if streamer.active_channels: - self._active_channels = streamer.active_channels.copy() - else: - self._active_channels = avail_channels - self._averaged_channels = self._active_channels - elif self._averaged_channels is not None: - self._averaged_channels = [ - ch for ch in self._averaged_channels if ch in self._active_channels - ] - - # Check for odd moving averaging window - if self._moving_average_width % 2 == 0: - self.log.warning('Moving average width ConfigOption must be odd integer number. ' - 'Changing value from {0:d} to {1:d}.' - ''.format(self._moving_average_width, self._moving_average_width + 1)) - self._moving_average_width += 1 - - # set settings in streamer hardware - self.set_channel_settings(self._active_channels, self._averaged_channels) - self.set_trace_settings(data_rate=self._data_rate) - # set up internal frame loop connection - self._sigNextDataFrame.connect(self._acquire_data_block, QtCore.Qt.QueuedConnection) - - def on_deactivate(self) -> None: - """ De-initialisation performed during deactivation of the module. - """ - try: - self._sigNextDataFrame.disconnect() - # Stop measurement - if self.module_state() == 'locked': - self._stop() - finally: - # Free (potentially) large raw data buffers - self._data_buffer = None - self._times_buffer = None - - def _init_data_arrays(self) -> None: - channel_count = len(self.active_channel_names) - averaged_channel_count = len(self._averaged_channels) - window_size = int(round(self._trace_window_size * self.data_rate)) - constraints = self.streamer_constraints - trace_dtype = np.float64 if is_integer_type(constraints.data_type) else constraints.data_type - - # processed data arrays - self._trace_data = np.zeros( - [window_size + self._moving_average_width // 2, channel_count], - dtype=trace_dtype - ) - self._trace_data_averaged = np.zeros( - [window_size - self._moving_average_width // 2, averaged_channel_count], - dtype=trace_dtype - ) - self._trace_times = np.arange(window_size, dtype=np.float64) - if constraints.sample_timing == SampleTiming.TIMESTAMP: - self._trace_times -= window_size - if constraints.sample_timing != SampleTiming.RANDOM: - self._trace_times /= self.data_rate - - # raw data buffers - self._data_buffer = np.empty(channel_count * self._channel_buffer_size, - dtype=constraints.data_type) - if constraints.sample_timing == SampleTiming.TIMESTAMP: - self._times_buffer = np.zeros(self._channel_buffer_size, dtype=np.float64) - else: - self._times_buffer = None - - @property - def streamer_constraints(self) -> DataInStreamConstraints: - """ Retrieve the hardware constrains from the counter device """ - # netobtain is required if streamer is a remote module - return netobtain(self._streamer().constraints) - - @property - def data_rate(self) -> float: - """ Data rate in Hz. The data rate describes the effective sample rate of the processed - sample trace taking into account oversampling: - - data_rate = hardware_sample_rate / oversampling_factor - """ - return self.sampling_rate / self.oversampling_factor - - @data_rate.setter - def data_rate(self, val: float) -> None: - self.set_trace_settings(data_rate=val) - - @property - def trace_window_size(self) -> float: - """ The size of the running trace window in seconds """ - return self._trace_window_size - - @trace_window_size.setter - def trace_window_size(self, val: Union[int, float]) -> None: - self.set_trace_settings(trace_window_size=val) - - @property - def moving_average_width(self) -> int: - """ The width of the moving average filter in samples. Must be an odd number. """ - return self._moving_average_width - - @moving_average_width.setter - def moving_average_width(self, val: int) -> None: - self.set_trace_settings(moving_average_width=val) - - @property - def data_recording_active(self) -> bool: - """ Read-only bool flag indicating active data logging so it can be saved to file later """ - return self._data_recording_active - - @property - def oversampling_factor(self) -> int: - """ This integer value determines how many times more samples are acquired by the hardware - and averaged before being processed by the trace logic. - An oversampling factor <= 1 means no oversampling is performed. - """ - return self._oversampling_factor - - @oversampling_factor.setter - def oversampling_factor(self, val: int) -> None: - self.set_trace_settings(oversampling_factor=val) - - @property - def sampling_rate(self) -> float: - """ Read-only property returning the actually set sample rate of the streaming hardware. - If not oversampling is used, this should be the same value as data_rate. - If oversampling is active, this value will be larger (by the oversampling factor) than - data_rate. - """ - return self._streamer().sample_rate - - @property - def active_channel_names(self) -> List[str]: - """ Read-only property returning the currently active channel names """ - return netobtain(self._streamer().active_channels.copy()) - - @property - def averaged_channel_names(self) -> List[str]: - """ Read-only property returning the currently active and averaged channel names """ - return self._averaged_channels.copy() - - @property - def trace_data(self) -> Tuple[np.ndarray, Dict[str, np.ndarray]]: - """ Read-only property returning the x-axis of the data trace and a dictionary of the - corresponding trace data arrays for each channel - """ - data_offset = self._trace_data.shape[0] - self._moving_average_width // 2 - data = {ch: self._trace_data[:data_offset, i] for i, ch in - enumerate(self.active_channel_names)} - return self._trace_times, data - - @property - def averaged_trace_data(self) -> Tuple[np.ndarray, Dict[str, np.ndarray]]: - """ Read-only property returning the x-axis of the averaged data trace and a dictionary of - the corresponding averaged trace data arrays for each channel - """ - if not self.averaged_channel_names or self.moving_average_width <= 1: - return None, None - data = {ch: self._trace_data_averaged[:, i] for i, ch in - enumerate(self.averaged_channel_names)} - return self._trace_times[-self._trace_data_averaged.shape[0]:], data - - @property - def trace_settings(self) -> Dict[str, Union[int, float]]: - """ Read-only property returning the current trace settings as dictionary """ - return {'oversampling_factor' : self.oversampling_factor, - 'moving_average_width': self.moving_average_width, - 'trace_window_size' : self.trace_window_size, - 'data_rate' : self.data_rate} - - @property - def channel_settings(self) -> Tuple[List[str], List[str]]: - """ Read-only property returning the currently active channel names and the currently - averaged channel names. - """ - return self.active_channel_names, self.averaged_channel_names - - @QtCore.Slot(dict) - def set_trace_settings(self, - settings_dict: Optional[Mapping[str, Union[int, float]]] = None, - **kwargs) -> None: - """ Method to set new trace settings. - Can either provide (a subset of) trace_settings via dict as first positional argument - and/or as keyword arguments (**kwargs overwrites settings_dict for duplicate keys). - - See property trace_settings for valid setting keywords. - - Calling this method while a trace is running will stop the trace first and restart after - successful application of new settings. - """ - if self.data_recording_active: - self.log.warning('Unable to configure settings while data is being recorded.') - return - - # Flag indicating if the stream should be restarted - restart = self.module_state() == 'locked' - if restart: - self._stop() - - try: - # Refine and sanity check settings - settings = self.trace_settings - if settings_dict is not None: - settings.update(settings_dict) - settings.update(kwargs) - settings['oversampling_factor'] = int(settings['oversampling_factor']) - settings['moving_average_width'] = int(settings['moving_average_width']) - settings['data_rate'] = float(settings['data_rate']) - settings['trace_window_size'] = int(round(settings['trace_window_size'] * settings['data_rate'])) / settings['data_rate'] - - if settings['oversampling_factor'] < 1: - raise ValueError(f'Oversampling factor must be integer value >= 1 ' - f'(received: {settings["oversampling_factor"]:d})') - - if settings['moving_average_width'] < 1: - raise ValueError(f'Moving average width must be integer value >= 1 ' - f'(received: {settings["moving_average_width"]:d})') - if settings['moving_average_width'] % 2 == 0: - settings['moving_average_width'] += 1 - self.log.warning(f'Moving average window must be odd integer number in order to ' - f'ensure perfect data alignment. Increased value to ' - f'{settings["moving_average_width"]:d}.') - if settings['moving_average_width'] / settings['data_rate'] > settings['trace_window_size']: - self.log.warning(f'Moving average width ({settings["moving_average_width"]:d}) is ' - f'smaller than the trace window size. Will adjust trace window ' - f'size to match.') - settings['trace_window_size'] = float( - settings['moving_average_width'] / settings['data_rate'] - ) - - with self._threadlock: - # Apply settings to hardware if needed - self._streamer().configure( - active_channels=self.active_channel_names, - streaming_mode=StreamingMode.CONTINUOUS, - channel_buffer_size=self._channel_buffer_size, - sample_rate=settings['data_rate'] * settings['oversampling_factor'] - ) - # update actually set values - self._oversampling_factor = settings['oversampling_factor'] - self._moving_average_width = settings['moving_average_width'] - self._trace_window_size = settings['trace_window_size'] - self.__moving_filter = np.full(shape=self._moving_average_width, - fill_value=1.0 / self._moving_average_width) - self._samples_per_frame = max(1, int(round(self.data_rate / self._max_frame_rate))) - self._init_data_arrays() - except: - self.log.exception('Error while trying to configure new trace settings:') - raise - finally: - self.sigTraceSettingsChanged.emit(self.trace_settings) - if restart: - self.start_reading() - else: - self.sigDataChanged.emit(*self.trace_data, *self.averaged_trace_data) - - @QtCore.Slot(list, list) - def set_channel_settings(self, enabled: Sequence[str], averaged: Sequence[str]) -> None: - """ Method to set new channel settings by providing a sequence of active channel names - (enabled) as well as a sequence of channel names to be averaged (averaged). - - Calling this method while a trace is running will stop the trace first and restart after - successful application of new settings. - """ - if self.data_recording_active: - self.log.warning('Unable to configure settings while data is being recorded.') - return - - # Flag indicating if the stream should be restarted - restart = self.module_state() == 'locked' - if restart: - self._stop() - - try: - self._streamer().configure( - active_channels=enabled, - streaming_mode=StreamingMode.CONTINUOUS, - channel_buffer_size=self._channel_buffer_size, - sample_rate=self.sampling_rate - ) - self._averaged_channels = [ch for ch in averaged if ch in enabled] - self._init_data_arrays() - except: - self.log.exception('Error while trying to configure new channel settings:') - raise - finally: - self.sigChannelSettingsChanged.emit(*self.channel_settings) - if restart: - self.start_reading() - else: - self.sigDataChanged.emit(*self.trace_data, *self.averaged_trace_data) - - @QtCore.Slot() - def start_reading(self) -> None: - """ Start data acquisition loop """ - with self._threadlock: - if self.module_state() == 'locked': - self.log.warning('Data acquisition already running. "start_reading" call ignored.') - self.sigStatusChanged.emit(True, self._data_recording_active) - return - - self.module_state.lock() - try: - if self._data_recording_active: - self._init_recording_arrays() - self._record_start_time = dt.datetime.now() - self._streamer().start_stream() - except: - self.module_state.unlock() - self.log.exception('Error while starting stream reader:') - raise - finally: - self._sigNextDataFrame.emit() - self.sigStatusChanged.emit(self.module_state() == 'locked', - self._data_recording_active) - - @QtCore.Slot() - def stop_reading(self) -> None: - """ Send a request to stop counting """ - with self._threadlock: - self._stop() - - def _stop(self) -> None: - if self.module_state() == 'locked': - try: - self._streamer().stop_stream() - except: - self.log.exception('Error while trying to stop stream reader:') - raise - finally: - self._stop_cleanup() - - def _stop_cleanup(self) -> None: - self.module_state.unlock() - self._stop_recording() - - @QtCore.Slot() - def _acquire_data_block(self) -> None: - """ This method gets the available data from the hardware. It runs repeatedly by being - connected to a QTimer timeout signal. - """ - with self._threadlock: - if self.module_state() == 'locked': - try: - streamer = self._streamer() - samples_to_read = max( - (streamer.available_samples // self._oversampling_factor) * self._oversampling_factor, - self._samples_per_frame * self._oversampling_factor - ) - samples_to_read = min( - samples_to_read, - (self._channel_buffer_size // self._oversampling_factor) * self._oversampling_factor - ) - # read the current counter values - if not self._streamer_is_remote: - # we can use the more efficient method of using a shared buffer - streamer.read_data_into_buffer(data_buffer=self._data_buffer, - samples_per_channel=samples_to_read, - timestamp_buffer=self._times_buffer) - else: - # streamer is remote, we need to have a new buffer created and passed to us - self._data_buffer, self._times_buffer = streamer.read_data( - samples_per_channel=samples_to_read - ) - self._data_buffer = netobtain(self._data_buffer) - self._times_buffer = netobtain(self._times_buffer) - - # Process data - channel_count = len(self.active_channel_names) - data_view = self._data_buffer[:channel_count * samples_to_read] - self._process_trace_data(data_view) - if self._times_buffer is None: - times_view = None - else: - times_view = self._times_buffer[:samples_to_read] - self._process_trace_times(times_view) - - if self._data_recording_active: - self._add_to_recording_array(data_view, times_view) - self.sigNewRawData.emit(data_view, times_view) - # Emit update signal - self.sigDataChanged.emit(*self.trace_data, *self.averaged_trace_data) - except Exception as e: - self.log.warning(f'Reading data from streamer went wrong: {e}') - self._stop_cleanup() - return - self._sigNextDataFrame.emit() - - def _process_trace_times(self, times_buffer: np.ndarray) -> None: - if self.oversampling_factor > 1: - times_buffer = times_buffer.reshape( - (times_buffer.size // self.oversampling_factor, self.oversampling_factor) - ) - times_buffer = np.mean(times_buffer, axis=1) - - # discard data outside time frame - times_buffer = times_buffer[-self._trace_times.size:] - - # Roll data array to have a continuously running time trace - self._trace_times = np.roll(self._trace_times, -times_buffer.size) - # Insert new data - self._trace_times[-times_buffer.size:] = times_buffer - - def _process_trace_data(self, data_buffer: np.ndarray) -> None: - """ Processes raw data from the streaming device """ - channel_count = len(self.active_channel_names) - samples_per_channel = data_buffer.size // channel_count - data_view = data_buffer.reshape([samples_per_channel, channel_count]) - # Down-sample and average according to oversampling factor - if self.oversampling_factor > 1: - data_view = data_view.reshape( - [samples_per_channel // self.oversampling_factor, - self.oversampling_factor, - channel_count] - ) - data_view = np.mean(data_view, axis=1) - - # discard data outside time frame - data_view = data_view[-self._trace_data.shape[0]:, :] - new_channel_samples = data_view.shape[0] - - # Roll data array to have a continuously running time trace - self._trace_data = np.roll(self._trace_data, -new_channel_samples, axis=0) - # Insert new data - self._trace_data[-new_channel_samples:, :] = data_view - - # Calculate moving average by using numpy.convolve with a normalized uniform filter - if self.moving_average_width > 1 and self.averaged_channel_names: - # Only convolve the new data and roll the previously calculated moving average - self._trace_data_averaged = np.roll(self._trace_data_averaged, - -new_channel_samples, - axis=0) - offset = new_channel_samples + len(self.__moving_filter) - 1 - for i, ch in enumerate(self.averaged_channel_names): - data_index = self.active_channel_names.index(ch) - self._trace_data_averaged[-new_channel_samples:, i] = np.convolve( - self._trace_data[-offset:, data_index], - self.__moving_filter, - mode='valid' - ) - - def _init_recording_arrays(self) -> None: - constraints = self.streamer_constraints - try: - sample_bytes = np.finfo(constraints.data_type).bits // 8 - except ValueError: - sample_bytes = np.iinfo(constraints.data_type).bits // 8 - # Try to allocate space for approx. 10sec of samples (limited by ConfigOption) - channel_count = len(self.active_channel_names) - channel_samples = int(10 * self.sampling_rate) - data_byte_size = sample_bytes * channel_count * channel_samples - if constraints.sample_timing == SampleTiming.TIMESTAMP: - if (8 * channel_samples + data_byte_size) > self._max_raw_data_bytes: - channel_samples = max( - 1, - self._max_raw_data_bytes // (channel_count * sample_bytes + 8) - ) - self._recorded_raw_times = np.zeros(channel_samples, dtype=np.float64) - else: - if data_byte_size > self._max_raw_data_bytes: - channel_samples = max(1, self._max_raw_data_bytes // (channel_count * sample_bytes)) - self._recorded_raw_times = None - self._recorded_raw_data = np.empty(channel_count * channel_samples, - dtype=constraints.data_type) - self._recorded_sample_count = 0 - - def _expand_recording_arrays(self) -> int: - total_samples = self._recorded_raw_data.size - channel_count = len(self.active_channel_names) - current_samples = total_samples // channel_count - byte_granularity = channel_count * self._recorded_raw_data.itemsize - new_byte_size = 2 * current_samples * byte_granularity - if self._recorded_raw_times is not None: - new_byte_size += 2 * current_samples * self._recorded_raw_times.itemsize - byte_granularity += self._recorded_raw_times.itemsize - new_samples_per_channel = min(self._max_raw_data_bytes, new_byte_size) // byte_granularity - additional_samples = new_samples_per_channel - current_samples - self._recorded_raw_data = np.append( - self._recorded_raw_data, - np.empty(channel_count * additional_samples, dtype=self._recorded_raw_data.dtype), - ) - if self._recorded_raw_times is not None: - self._recorded_raw_times = np.append( - self._recorded_raw_times, - np.empty(additional_samples, dtype=self._recorded_raw_times.dtype) - ) - return additional_samples - - def _add_to_recording_array(self, data, times=None) -> None: - channel_count = len(self.active_channel_names) - free_samples_per_channel = (self._recorded_raw_data.size // channel_count) - \ - self._recorded_sample_count - new_samples = data.size // channel_count - total_new_samples = new_samples * channel_count - if new_samples > free_samples_per_channel: - free_samples_per_channel += self._expand_recording_arrays() - if new_samples > free_samples_per_channel: - self.log.error( - f'Configured maximum allowed amount of raw data reached ' - f'({self._max_raw_data_bytes:d} bytes). Saving raw data so far and terminating ' - f'data recording.' - ) - self._recorded_raw_data[channel_count * self._recorded_sample_count:] = data[:channel_count * free_samples_per_channel] - if self._recorded_raw_times is not None: - self._recorded_raw_times[self._recorded_sample_count:] = times[:free_samples_per_channel] - self._recorded_sample_count += free_samples_per_channel - self._stop_recording() - return - - begin = self._recorded_sample_count * channel_count - end = begin + total_new_samples - self._recorded_raw_data[begin:end] = data[:total_new_samples] - if self._recorded_raw_times is not None: - begin = self._recorded_sample_count - end = begin + new_samples - self._recorded_raw_times[begin:end] = times[:new_samples] - self._recorded_sample_count += new_samples - - @QtCore.Slot() - def start_recording(self): - """ Will start to continuously accumulate raw data from the streaming hardware (without - running average and oversampling). Data will be saved to file once the trace acquisition is - stopped. - If the streamer is not running it will be started in order to have data to save. - """ - with self._threadlock: - if self._data_recording_active: - self.sigStatusChanged.emit(self.module_state() == 'locked', True) - else: - self._data_recording_active = True - if self.module_state() == 'locked': - self._init_recording_arrays() - self._record_start_time = dt.datetime.now() - self.sigStatusChanged.emit(True, True) - else: - self.start_reading() - - @QtCore.Slot() - def stop_recording(self): - """ Stop the accumulative data recording and save data to file. Will not stop the data - streaming. Ignored if no stream is running (module is in idle state). - """ - with self._threadlock: - self._stop_recording() - - def _stop_recording(self) -> None: - try: - if self._data_recording_active: - self._save_recorded_data(save_figure=True) - finally: - self._data_recording_active = False - self.sigStatusChanged.emit(self.module_state() == 'locked', False) - - def _save_recorded_data(self, name_tag='', save_figure=True): - """ Save the recorded counter trace data and writes it to a file """ - try: - constraints = self.streamer_constraints - metadata = { - 'Start recoding time': self._record_start_time.strftime('%d.%m.%Y, %H:%M:%S.%f'), - 'Sample rate (Hz)' : self.sampling_rate, - 'Sample timing' : constraints.sample_timing.name - } - column_headers = [ - f'{ch} ({constraints.channel_units[ch]})' for ch in self.active_channel_names - ] - channel_count = len(column_headers) - nametag = f'data_trace_{name_tag}' if name_tag else 'data_trace' - - data = self._recorded_raw_data[:channel_count * self._recorded_sample_count].reshape( - [self._recorded_sample_count, channel_count] - ) - if self._recorded_raw_times is not None: - print(data.shape) - data = np.column_stack( - [self._recorded_raw_times[:self._recorded_sample_count], data] - ) - print(data.shape, '\n') - column_headers.insert(0, 'Time (s)') - try: - fig = self._draw_raw_data_thumbnail(data) if save_figure else None - finally: - storage = TextDataStorage(root_dir=self.module_default_data_dir) - filepath, _, _ = storage.save_data(data, - metadata=metadata, - nametag=nametag, - column_headers=column_headers) - if fig is not None: - storage.save_thumbnail(mpl_figure=fig, file_path=filepath) - except: - self.log.exception('Something went wrong while saving raw data:') - raise - - def _draw_raw_data_thumbnail(self, data: np.ndarray) -> plt.Figure: - """ Draw figure to save with data file """ - constraints = self.streamer_constraints - # Handle excessive data size for plotting. Artefacts may occur due to IIR decimation filter. - decimate_factor = 0 - while data.shape[0] >= 20000: - print(data.shape[0]) - decimate_factor += 2 - data = decimate(data, q=2, axis=0) - - if constraints.sample_timing == SampleTiming.RANDOM: - x = np.arange(data.shape[0]) - x_label = 'Sample Index' - elif constraints.sample_timing == SampleTiming.CONSTANT: - if decimate_factor > 0: - x = np.arange(data.shape[0]) / (self.sampling_rate / decimate_factor) - else: - x = np.arange(data.shape[0]) / self.sampling_rate - x_label = 'Time (s)' - else: - x = data[:, 0] - data[0, 0] - data = data[:, 1:] - x_label = 'Time (s)' - # Create figure and scale data - max_abs_value = ScaledFloat(max(data.max(), np.abs(data.min()))) - if max_abs_value.scale: - data = data / max_abs_value.scale_val - y_label = f'Signal ({max_abs_value.scale}arb.u.)' - else: - y_label = 'Signal (arb.u.)' - - fig, ax = plt.subplots() - ax.plot(x, data, linestyle='-', marker='', linewidth=0.5) - ax.set_xlabel(x_label) - ax.set_ylabel(y_label) - return fig - - @QtCore.Slot() - def save_trace_snapshot(self, name_tag: Optional[str] = '', save_figure: Optional[bool] = True): - """ A snapshot of the current data trace window will be saved """ - try: - timestamp = dt.datetime.now() - constraints = self.streamer_constraints - metadata = { - 'Timestamp': timestamp.strftime('%d.%m.%Y, %H:%M:%S.%f'), - 'Data rate (Hz)': self.data_rate, - 'Oversampling factor (samples)': self.oversampling_factor, - 'Sampling rate (Hz)': self.sampling_rate - } - column_headers = [ - f'{ch} ({constraints.channel_units[ch]})' for ch in self.active_channel_names - ] - nametag = f'trace_snapshot_{name_tag}' if name_tag else 'trace_snapshot' - - data_offset = self._trace_data.shape[0] - self._moving_average_width // 2 - data = self._trace_data[:data_offset, :] - x = self._trace_times - try: - fig = self._draw_trace_snapshot_thumbnail(x, data) if save_figure else None - finally: - if constraints.sample_timing != SampleTiming.RANDOM: - data = np.column_stack([x, data]) - column_headers.insert(0, 'Time (s)') - - storage = TextDataStorage(root_dir=self.module_default_data_dir) - filepath, _, _ = storage.save_data(data, - timestamp=timestamp, - metadata=metadata, - nametag=nametag, - column_headers=column_headers) - if fig is not None: - storage.save_thumbnail(mpl_figure=fig, file_path=filepath) - except: - self.log.exception('Something went wrong while saving trace snapshot:') - raise - - def _draw_trace_snapshot_thumbnail(self, x: np.ndarray, data: np.ndarray) -> plt.Figure: - """ Draw figure to save with data file """ - if self.streamer_constraints.sample_timing == SampleTiming.RANDOM: - x_label = 'Sample Index' - else: - x_label = 'Time (s)' - - # Create figure and scale data - max_abs_value = ScaledFloat(max(data.max(), np.abs(data.min()))) - if max_abs_value.scale: - data = data / max_abs_value.scale_val - y_label = f'Signal ({max_abs_value.scale}arb.u.)' - else: - y_label = 'Signal (arb.u.)' - - fig, ax = plt.subplots() - ax.plot(x, data, linestyle='-', marker='', linewidth=0.5) - ax.set_xlabel(x_label) - ax.set_ylabel(y_label) - return fig diff --git a/src/qudi/logic/common/fast_counter_logic.py b/src/qudi/logic/common/fast_counter_logic.py index 2acb738..e64533e 100644 --- a/src/qudi/logic/common/fast_counter_logic.py +++ b/src/qudi/logic/common/fast_counter_logic.py @@ -83,6 +83,7 @@ def start_counter(self): counter.start_measure() self.__timer.start(int(self._record_length_s*1000)) self.sigScanStarted.emit() + @QtCore.Slot() def stop_counter(self): @@ -96,6 +97,6 @@ def stop_counter(self): def __update_data(self): with self._thread_lock: counter = self._counter() - self._last_data = counter.get_data_trace() + self._last_data = np.squeeze(counter.get_data_trace()) self.__timer.start(int(self._record_length_s*1000)) self.sigScanFinished.emit(self._last_data) \ No newline at end of file diff --git a/src/qudi/logic/common/wavemeter_logic.py b/src/qudi/logic/common/wavemeter_logic.py new file mode 100644 index 0000000..febd9e9 --- /dev/null +++ b/src/qudi/logic/common/wavemeter_logic.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +""" +A module for controlling a scanning laser. + +Copyright (c) 2025, QuPIDC qudi developers. + +Qudi is free software: you can redistribute it and/or modify it under the terms of +the GNU Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along with qudi. +If not, see . +""" + + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +from PySide2 import QtCore +from qudi.core.connector import Connector +from qudi.core.configoption import ConfigOption +from qudi.core.statusvariable import StatusVar +from qudi.util.mutex import RecursiveMutex +from qudi.core.module import LogicBase + +class WavemeterLogic(LogicBase): + """ Logic class for controlling a camera. + + Example config for copy-paste: + + wavemeter_logic: + module.Class: 'common.wavemeter_logic.WavemeterLogic' + connect: + wavemeter: wavemeter_dummy + + """ + + # declare connectors + _wavemeter = Connector(name='wavemeter', interface='SimpleWavemeterInterface') + + # signals + sigNewData = QtCore.Signal(np.ndarray) + + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def on_activate(self): + """ Initialisation performed during activation of the module. + """ + wavemeter = self._wavemeter() + self._wavelengths = wavemeter.get_wavelengths() + + wavemeter.sigWavelengthUpdated.connect(self._update_data) + + + def on_deactivate(self): + """ Perform required deactivation. """ + pass + + @property + def wavelengths(self) -> np.ndarray: + return self._wavelengths + + @QtCore.Slot(np.ndarray) + def _update_data(self, data: np.ndarray): + if self.module_state() == 'locked': + self._wavelengths = data + self.sigNewData.emit(data) + + @QtCore.Slot() + def start_reading(self): + if self.module_state() == 'idle': + self.module_state.lock() + + @QtCore.Slot() + def stop_reading(self): + if self.module_state() == 'locked': + self.module_state.unlock() \ No newline at end of file diff --git a/src/qudi/logic/terascan_logic.py b/src/qudi/logic/terascan_logic.py index 16fff97..fb5ed0d 100644 --- a/src/qudi/logic/terascan_logic.py +++ b/src/qudi/logic/terascan_logic.py @@ -35,7 +35,7 @@ class TerascanLogic(LogicBase): module.Class: 'terascan_logic.TerascanLogic' connect: laser: scanning_laser_logic - wavemeter: time_series_reader_logic + wavemeter: wavemeter_logic counter: fast_counter_logic daq: daq_reader_logic # Note that this logic assumes there is exactly one (digital) input to the DAQ. options: @@ -45,14 +45,14 @@ class TerascanLogic(LogicBase): # declare connectors _laser = Connector(name='laser', interface='ScanningLaserLogic') - _wavemeter = Connector(name='wavemeter', interface='DataInStreamLogic') + _wavemeter = Connector(name='wavemeter', interface='WavemeterLogic') _counter = Connector(name='counter', interface='FastCounterLogic') _daq = Connector(name='daq', interface='DAQReaderLogic') # declare config options _record_length_ms = ConfigOption(name='record_length_ms', - default=1, - missing='info') + default=1, + missing='info') # status variables: _start_wavelength = StatusVar('start_wavelength', default=0.75) @@ -66,7 +66,9 @@ class TerascanLogic(LogicBase): # Update signals, e.g. for GUI module sigWavelengthUpdated = QtCore.Signal(float) sigCountsUpdated = QtCore.Signal(object) # is a List[TerascanData] + sigLaserLocked = QtCore.Signal(bool) + # Update signals for other logics sigConfigureCounter = QtCore.Signal(float, float) sigConfigureLaser = QtCore.Signal(float, float) @@ -107,7 +109,7 @@ def on_activate(self): # Inputs: laser.sigScanStarted.connect(self._laser_scan_started) laser.sigScanFinished.connect(self._laser_scan_finished) - wavemeter.sigNewRawData.connect(self._new_wavemeter_data) + wavemeter.sigNewData.connect(self._new_wavemeter_data) counter.sigScanFinished.connect(self._process_counter_data) daq.sigNewData.connect(self._new_daq_data) @@ -119,6 +121,9 @@ def on_activate(self): def on_deactivate(self): pass + @property + def locked(self) -> bool: + return self._laser_locked @QtCore.Slot() def start_scan(self): @@ -159,11 +164,11 @@ def _laser_scan_finished(self): self.sigScanFinished.emit() self.module_state.unlock() - @QtCore.Slot(object, object) - def _new_wavemeter_data(self, data: object, timestamps: object): + @QtCore.Slot(np.ndarray) + def _new_wavemeter_data(self, data: np.ndarray): with self._thread_lock: if self.module_state() == 'locked' and self._laser_locked: - wave = np.average(data) + wave = data[0][0] self._current_wavelength = wave # ? self.sigWavelengthUpdated.emit(wave) @@ -188,8 +193,10 @@ def _new_daq_data(self, data: List[ReaderVal]): # We assume there is only one digital input for this measurement if i.val and not self._laser_locked: self._laser_locked = True - self.sigStartCounting.emit() + # self.sigStartCounting.emit() if not i.val and self._laser_locked: self._laser_locked = False - self.sigStopCounting.emit() + # self.sigStopCounting.emit() + + self.sigLaserLocked.emit(self._laser_locked)