From 0e572622a417bc1a39bf0344ded404538e27977f Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 7 Dec 2022 09:14:47 +0100 Subject: [PATCH 01/81] fix raised error when laser_index recovered from statusVar doesn't fit current config --- src/qudi/gui/pulsed/pulsed_maingui.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/qudi/gui/pulsed/pulsed_maingui.py b/src/qudi/gui/pulsed/pulsed_maingui.py index b2c1bd0f7..d6463553c 100644 --- a/src/qudi/gui/pulsed/pulsed_maingui.py +++ b/src/qudi/gui/pulsed/pulsed_maingui.py @@ -3173,6 +3173,7 @@ def update_laser_data(self): @return: """ + self._sanatize_laser_index() laser_index = self._pe.laserpulses_ComboBox.currentIndex() show_raw = self._pe.laserpulses_display_raw_CheckBox.isChecked() is_gated = len(self.pulsedmasterlogic().raw_data.shape) > 1 @@ -3207,6 +3208,12 @@ def slot(): self.generate_predefined_clicked(method_name, sample_and_load) return slot + def _sanatize_laser_index(self): + laser_index = self._pe.laserpulses_ComboBox.currentIndex() + if laser_index not in range(len(self.pulsedmasterlogic().laser_data)): + laser_index = 0 + self._pe.laserpulses_ComboBox.setCurrentIndex(laser_index) + @QtCore.Slot() def run_pg_benchmark(self): From a50a762007f0be430faf3ac8d6eb1f92201772a0 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 7 Dec 2022 09:25:54 +0100 Subject: [PATCH 02/81] small improvements to awg819x - set external clock by config option - disable dynamic sequence mode by config option --- src/qudi/hardware/awg/keysight_m819x.py | 45 +++++++++++++++++-------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/qudi/hardware/awg/keysight_m819x.py b/src/qudi/hardware/awg/keysight_m819x.py index 788d9524f..69f2b290a 100644 --- a/src/qudi/hardware/awg/keysight_m819x.py +++ b/src/qudi/hardware/awg/keysight_m819x.py @@ -63,9 +63,12 @@ class AWGM819X(PulserInterface): _wave_mem_mode = None _wave_file_extension = '.bin' _wave_transfer_datatype = 'h' + _dynamic_sequence_mode = ConfigOption(name='dynamic_sequence_mode', default=True, missing='nothing') # explicitly set low/high levels for [[d_ch1_low, d_ch1_high], [d_ch2_low, d_ch2_high], ...] _d_ch_level_low_high = ConfigOption(name='d_ch_level_low_high', default=[], missing='nothing') + _ext_ref_clock_freq = ConfigOption(name='ext_ref_clock_freq', default=None, missing='nothing') + def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) @@ -179,6 +182,7 @@ def pulser_on(self): class variable status_dic.) """ self._write_output_on() + self.check_dev_error() # Sec. 6.4 from manual: # In the program it is recommended to send the command for starting @@ -320,7 +324,8 @@ def load_sequence(self, sequence_name): select the first segment in your sequence, before any dynamic sequence selection. """ self.write_all_ch(":STAB{}:SEQ:SEL 0", all_by_one={'m8195a': True}) - self.write_all_ch(":STAB{}:DYN ON", all_by_one={'m8195a': True}) + if self._dynamic_sequence_mode: + self.write_all_ch(":STAB{}:DYN ON", all_by_one={'m8195a': True}) return 0 @@ -927,12 +932,12 @@ def write_sequence(self, name, sequence_parameters): control = self._get_sequence_control_bin(sequence_parameters, index) - seq_loop_count = 1 + sequence_loop_count = 1 if seq_step.repetitions == -1: # this is ugly, limits maximal waiting time. 1 Sa -> approx. 0.3 s - seg_loop_count = 4294967295 # max value, todo: from constraints + segment_loop_count = 4294967295 # max value, todo: from constraints else: - seg_loop_count = seq_step.repetitions + 1 # if repetitions = 0 then do it once + segment_loop_count = seq_step.repetitions + 1 # if repetitions = 0 then do it once seg_start_offset = 0 # play whole segement from start... seg_end_offset = 0xFFFFFFFF # to end @@ -952,8 +957,8 @@ def write_sequence(self, name, sequence_parameters): self.write(':STAB:DATA {0}, {1}, {2}, {3}, {4}, {5}, {6}' .format(index, control, - seq_loop_count, - seg_loop_count, + sequence_loop_count, + segment_loop_count, segment_id_ch1, seg_start_offset, seg_end_offset)) @@ -961,8 +966,8 @@ def write_sequence(self, name, sequence_parameters): self.write(':STAB2:DATA {0}, {1}, {2}, {3}, {4}, {5}, {6}' .format(index, control, - seq_loop_count, - seg_loop_count, + sequence_loop_count, + segment_loop_count, segment_id_ch2, seg_start_offset, seg_end_offset)) @@ -1209,8 +1214,7 @@ def _init_device(self): self.reset() constr = self.get_constraints() - self.write(':ROSC:SOUR INT') # Chose source for reference clock - + self._set_ref_clock() self._set_awg_mode() # General procedure according to Sec. 8.22.6 in AWG8190A manual: @@ -1243,6 +1247,15 @@ def _init_device(self): self._segment_table = [[], []] # [0]: ch1, [1]: ch2. Local, read-only copy of the device segment table self._flag_segment_table_req_update = True # local copy requires update + def _set_ref_clock(self): + if self._ext_ref_clock_freq is None: + self.write(':ROSC:SOUR INT') + else: + self._ext_ref_clock_freq = int(self._ext_ref_clock_freq) + self.write(':ROSC:SOUR EXT') + self.write(f':ROSC:FREQ {self._ext_ref_clock_freq:d}') + self.log.debug(f"Setting to external ref clock with f= {self._ext_ref_clock_freq/1e6} MHz") + def _load_list_2_dict(self, load_dict): def _create_load_dict_allch(load_dict): @@ -1372,9 +1385,9 @@ def check_dev_error(self): for i in range(30): # error buffer of device is 30 raw_str = self.query(':SYST:ERR?', force_no_check=True) - is_error = not ('0' in raw_str[0]) + is_error = not ('0,' == raw_str[0:2]) if is_error: - self.log.warn("AWG issued error: {}".format(raw_str)) + self.log.warning("AWG issued error: {}".format(raw_str)) has_error_occured = True else: break @@ -2189,9 +2202,9 @@ def get_constraints(self): # manual 1.5.4: Depending on the Sample Rate Divider, the 256 sample wide output of the sequencer # is divided by 1, 2 or 4. constraints.waveform_length.step = 256 / self._sample_rate_div - constraints.waveform_length.min = 1280 # != p 108 manual, but tested manually ('MARK') + constraints.waveform_length.min = 1280 / self._sample_rate_div # != p 108 manual, but tested manually ('MARK') constraints.waveform_length.max = int(16e9) - constraints.waveform_length.default = 1280 + constraints.waveform_length.default = 1280 / self._sample_rate_div # analog channel constraints.a_ch_amplitude.min = 0.075 # from soft frontpanel @@ -2820,6 +2833,10 @@ def _get_sequence_control_bin(self, sequence_parameters, idx_step): if 'pattern_jump_address' in next_step: control = 0x1 << 30 + if 'segment_advance_mode' in seq_step: + if seq_step['segment_advance_mode'] == 'conditional': + control += 0x1 << 16 + control += 0x1 << 24 # always enable markers return control From ad6c0b926b63e0cdca1582feade6210c6ae0f4de Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 7 Dec 2022 09:30:42 +0100 Subject: [PATCH 03/81] workaround to fix driver issue in sequential mode --- src/qudi/hardware/fastcomtec/fastcomtecmcs6.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py b/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py index c2ec85eab..236b37fd3 100644 --- a/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py +++ b/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py @@ -710,7 +710,12 @@ def set_cycle_mode(self, sequential_mode=True, cycles=None): # Turn on or off sequential cycle mode if sequential_mode: - cmd = 'sweepmode={0}'.format(hex(1978500)) + self.log.debug("Sequential mode enabled. Make sure to set 'checksync=0' in mcs6a.ini. " + "Resyncing can cause issues in sequential mode on some driver versions.") + # old settings (dec=1978500) + disable "sweep counter not needed" + disable "allow 6 byte words" + raw_bytes_dec = 35528836 + cmd = 'sweepmode={0}'.format(hex(raw_bytes_dec)) + self.log.debug(f"Sweepmode set to: {raw_bytes_dec}") else: cmd = 'sweepmode={0}'.format(hex(1978496)) self.dll.RunCmd(0, bytes(cmd, 'ascii')) From 69e334b6867f43bd9b80a01dcc6e48060183ca45 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 7 Dec 2022 09:41:22 +0100 Subject: [PATCH 04/81] include fix from qudi PR #708 --- src/qudi/logic/poi_manager_logic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qudi/logic/poi_manager_logic.py b/src/qudi/logic/poi_manager_logic.py index 48a2c510f..7d3c6e135 100644 --- a/src/qudi/logic/poi_manager_logic.py +++ b/src/qudi/logic/poi_manager_logic.py @@ -1138,11 +1138,11 @@ def load_roi(self, complete_path=None): filetag = filename.rsplit('_poi_list.dat', 1)[0] # Read POI data as well as roi metadata from textfile - poi_names = np.loadtxt(complete_path, delimiter='\t', usecols=0, dtype=str) + poi_names = np.loadtxt(complete_path, delimiter='\t', usecols=0, dtype=str, ndmin=1) if is_legacy_format: - poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(2, 3, 4), dtype=float) + poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(2, 3, 4), dtype=float, ndmin=2) else: - poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(1, 2, 3), dtype=float) + poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(1, 2, 3), dtype=float, ndmin=2) # Create list of POI instances poi_list = [PointOfInterest(pos, poi_names[i]) for i, pos in enumerate(poi_coords)] From 648fba893904572abdf9263e09f1b2664d581e49 Mon Sep 17 00:00:00 2001 From: Neverhorst Date: Mon, 30 Jan 2023 17:37:59 +0100 Subject: [PATCH 05/81] Example implementation of coordinate transformation functionality in the ScanningProbeInterface. --- .../interface/scanning_probe_interface.py | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 8763d2a71..3599c3f02 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -31,6 +31,18 @@ class ScanningProbeInterface(Base): A scanner device is hardware that can move multiple axes. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._coordinate_transform = None + + def coordinate_transform(self, val, inverse=False): + if self._coordinate_transform is None: + return val + return self._coordinate_transform(val, inverse) + + def set_coordinate_transform(self, transform_func): + if transform_func is not None: + raise ValueError('Coordinate transformation not supported by scanning hardware.') @abstractmethod def get_constraints(self): @@ -535,7 +547,7 @@ class ScanConstraints: """ def __init__(self, axes, channels, backscan_configurable, has_position_feedback, - square_px_only): + square_px_only, allow_coordinate_transform): """ """ if not all(isinstance(ax, ScannerAxis) for ax in axes): @@ -548,11 +560,14 @@ def __init__(self, axes, channels, backscan_configurable, has_position_feedback, raise TypeError('Parameter "has_position_feedback" must be of type bool.') if not isinstance(square_px_only, bool): raise TypeError('Parameter "square_px_only" must be of type bool.') + if not isinstance(allow_coordinate_transform, bool): + raise TypeError('Parameter "allow_coordinate_transform" must be of type bool.') self._axes = {ax.name: ax for ax in axes} self._channels = {ch.name: ch for ch in channels} self._backscan_configurable = bool(backscan_configurable) self._has_position_feedback = bool(has_position_feedback) self._square_px_only = bool(square_px_only) + self._allow_coordinate_transform = bool(allow_coordinate_transform) @property def axes(self): @@ -573,3 +588,37 @@ def has_position_feedback(self): # TODO Incorporate in gui/logic toolchain? @property def square_px_only(self): # TODO Incorporate in gui/logic toolchain? return self._square_px_only + + @property + def allow_coordinate_transform(self): + return self._allow_coordinate_transform + + +class CoordinateTransformMixin: + """ Can be used by concrete hardware modules to facilitate coordinate transformation, except + for performing scans. + The transformation for scanning can be either implemented in the base or in the mixed hardware + module. + + Usage: + MyTransformationScanner(CoordinateTransformMixin, MyScanner): + pass + """ + def set_coordinate_transform(self, transform_func): + # ToDo: Proper sanity checking here, e.g. function signature etc. + if transform_func is not None and not callable(transform_func): + raise ValueError('Coordinate transformation function must be callable with ' + 'signature "coordinate_transform(value, inverse=False)"') + self._coordinate_transform = transform_func + + def move_absolute(self, position, velocity=None, blocking=False): + return super().move_absolute(self.coordinate_transform(position), velocity, blocking) + + def move_relative(self, distance, velocity=None, blocking=False): + return super().move_relative(self.coordinate_transform(distance), velocity, blocking) + + def get_target(self): + return self.coordinate_transform(super().get_target(), inverse=True) + + def get_position(self): + return self.coordinate_transform(super().get_position(), inverse=True) From 2d1c2c0ee32c1c307186fdc22ce4ab44ebb60f1b Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 3 Feb 2023 17:29:09 +0100 Subject: [PATCH 06/81] set up relevant functions. ScanningProbeDummyCorrected can perform a toy transformation. --- .../hardware/dummy/scanning_probe_dummy.py | 42 +++++++++++++++++-- .../interfuse/ni_scanning_probe_interfuse.py | 16 ++++++- .../interface/scanning_probe_interface.py | 15 +++++++ src/qudi/logic/scanning_probe_logic.py | 19 +++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 38da20f8f..5d1b64a0c 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -25,7 +25,7 @@ from fysom import FysomError from qudi.core.configoption import ConfigOption from qudi.util.mutex import RecursiveMutex -from qudi.interface.scanning_probe_interface import ScanningProbeInterface, ScanData +from qudi.interface.scanning_probe_interface import ScanningProbeInterface, ScanData, CoordinateTransformMixin from qudi.interface.scanning_probe_interface import ScanConstraints, ScannerAxis, ScannerChannel @@ -127,7 +127,8 @@ def on_activate(self): channels=channels, backscan_configurable=False, has_position_feedback=False, - square_px_only=False) + square_px_only=False, + allow_coordinate_transform=self.supports_coordinate_transform) self.__scan_start = 0 self.__last_line = -1 self.__update_timer = QtCore.QTimer() @@ -364,7 +365,7 @@ def start_scan(self): self._current_scan_resolution[1]) else: y_values = np.linspace(self._current_position['y'], self._current_position['y'], 1) - xy_grid = np.meshgrid(x_values, y_values, indexing='ij') + xy_grid = self._init_scan_grid(x_values, y_values) include_dist = self._spot_size_dist[0] + 5 * self._spot_size_dist[1] self._scan_image = np.random.uniform(0, 2e4, self._current_scan_resolution) @@ -506,6 +507,8 @@ def __stop_timer(self): QtCore.Qt.BlockingQueuedConnection) else: self.__update_timer.stop() + def _init_scan_grid(self, x_values, y_values): + return np.meshgrid(x_values, y_values, indexing='ij') @staticmethod def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): @@ -519,3 +522,36 @@ def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): y_prime = y - y0 return offset + amp * np.exp( -(a * x_prime ** 2 + 2 * b * x_prime * y_prime + c * y_prime ** 2)) + + +class ScanningProbeDummyCorrected(CoordinateTransformMixin, ScanningProbeDummy): + + def set_coordinate_transform(self, transform_func): + # todo: if set with a transform_func, this dummy should + # transform the scan grid below + + if transform_func == 'debug': + transform_func = self.__func_debug_transform() + self.log.info("Set test functions for coord transform") + + super().set_coordinate_transform(transform_func) + + def _init_scan_grid(self, x_values, y_values): + # todo: this is demonstration only, not really a transformation + grid = np.meshgrid(2 * x_values, y_values, indexing='ij') + print(f"Transforming grid: {grid}") + + return grid + + def __func_debug_transform(self): + def transform_to(coord, inverse=False): + # this is a stub function + if inverse: + return {key: 0.5 * val for key, val in coord.items()} + else: + return {key: 2 * val for key, val in coord.items()} + return transform_to + + + + diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 0bf01fdf4..1a3450e2a 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -28,7 +28,7 @@ from PySide2.QtGui import QGuiApplication from qudi.interface.scanning_probe_interface import ScanningProbeInterface, ScanConstraints, \ - ScannerAxis, ScannerChannel, ScanData + ScannerAxis, ScannerChannel, ScanData, CoordinateTransformMixin from qudi.core.configoption import ConfigOption from qudi.core.connector import Connector from qudi.util.mutex import RecursiveMutex, Mutex @@ -160,7 +160,8 @@ def on_activate(self): channels=channels, backscan_configurable=False, # TODO incorporate in scanning_probe toolchain has_position_feedback=False, # TODO incorporate in scanning_probe toolchain - square_px_only=False) # TODO incorporate in scanning_probe toolchain + square_px_only=False, + allow_coordinate_transform=self.supports_coordinate_transform) # TODO incorporate in scanning_probe toolchain # self._target_pos = self.get_position() # get voltages/pos from ni_ao self._toggle_ao_setpoint_channels(False) # And free ao resources after that @@ -923,3 +924,14 @@ def is_full(self): return self.number_of_non_nan_values == self.frame_size +class NiScanningProbeInterfuseCorrected(CoordinateTransformMixin, NiScanningProbeInterfuse): + + def set_coordinate_transform(self, transform_func): + # todo: if set with a transform_func + # pass, the _init_ni_scan_array must change + pass + + + def _initialize_ni_scan_arrays(self, scan_data): + # todo + pass diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 3599c3f02..b0ac61001 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -44,6 +44,20 @@ def set_coordinate_transform(self, transform_func): if transform_func is not None: raise ValueError('Coordinate transformation not supported by scanning hardware.') + @property + def supports_coordinate_transform(self): + # todo: this should be part of the scanconstraints + # however this function is general for every scanner, but can't live + # in ScanContraints, as no handle to set_coordinate_transform + flag = False + try: + self.set_coordinate_transform(lambda x, inverse: x) + self.set_coordinate_transform(None) + flag = True + except ValueError: + pass + return flag + @abstractmethod def get_constraints(self): """ Get hardware constraints/limitations. @@ -567,6 +581,7 @@ def __init__(self, axes, channels, backscan_configurable, has_position_feedback, self._backscan_configurable = bool(backscan_configurable) self._has_position_feedback = bool(has_position_feedback) self._square_px_only = bool(square_px_only) + # todo: do we need this in the ScanContraints or is in scan interface enough? self._allow_coordinate_transform = bool(allow_coordinate_transform) @property diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index a5090757d..cce79ac16 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -26,6 +26,7 @@ from qudi.core.module import LogicBase from qudi.util.mutex import RecursiveMutex +from qudi.util.linear_transform import LinearTransformation # lives in branch qudi-core:coord-transforma from qudi.core.connector import Connector from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar @@ -326,6 +327,24 @@ def toggle_scan(self, start, scan_axes, caller_id=None): return self.start_scan(scan_axes, caller_id) return self.stop_scan() + def toggle_tilt_correction(self, enable=True): + # todo: this function should be general for n axes depending on config of self._scan_axes + # todo: should do a sensible rotation + + coord_transform = LinearTransformation(dimensions=len(self.scanner_axes)) + func = coord_transform.rotate + if enable: + self._scanner().set_coordinate_transform(func) + else: + self._scanner().set_coordinate_transform(None) + + def configure_tilt_correction(self, matrix=None, support_vecs=None): + # todo: should calculate the needed transformation from either + # 1. a) single support vector defining z plane + # b) 3 more support vectors defining a set of planes + # 2. a reotation matrix + pass + def _update_scan_settings(self, scan_axes, settings): for ax_index, ax in enumerate(scan_axes): # Update scan ranges if needed From a83efda176ecc2a30cc30b925df733c1e6577700 Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 10 Feb 2023 09:38:12 +0100 Subject: [PATCH 07/81] move debug coord transform to scanning logic and use a real rotation --- .../hardware/dummy/scanning_probe_dummy.py | 25 +++---------- .../interfuse/ni_scanning_probe_interfuse.py | 8 +++- src/qudi/logic/scanning_probe_logic.py | 37 +++++++++++++++++-- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 5d1b64a0c..6b73da1e6 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -526,31 +526,18 @@ def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): class ScanningProbeDummyCorrected(CoordinateTransformMixin, ScanningProbeDummy): - def set_coordinate_transform(self, transform_func): - # todo: if set with a transform_func, this dummy should - # transform the scan grid below - - if transform_func == 'debug': - transform_func = self.__func_debug_transform() - self.log.info("Set test functions for coord transform") - - super().set_coordinate_transform(transform_func) - def _init_scan_grid(self, x_values, y_values): # todo: this is demonstration only, not really a transformation - grid = np.meshgrid(2 * x_values, y_values, indexing='ij') + + vectors = {'x': x_values, 'y': y_values, 'z': np.zeros(len(x_values))} + vectors_tilted = self.coordinate_transform(vectors) + + grid = np.meshgrid(vectors_tilted['x'], vectors_tilted['y'], indexing='ij') print(f"Transforming grid: {grid}") return grid - def __func_debug_transform(self): - def transform_to(coord, inverse=False): - # this is a stub function - if inverse: - return {key: 0.5 * val for key, val in coord.items()} - else: - return {key: 2 * val for key, val in coord.items()} - return transform_to + diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 1a3450e2a..9cabb1245 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -933,5 +933,9 @@ def set_coordinate_transform(self, transform_func): def _initialize_ni_scan_arrays(self, scan_data): - # todo - pass + # todo: complete pseudo code below + #vectors = super()._initialize_ni_scan_arrays() + #vectors_transormed = self.coordinate_transform(vectors) + #return vectors_transormed + + pass \ No newline at end of file diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index cce79ac16..9ad0d82bc 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -26,7 +26,7 @@ from qudi.core.module import LogicBase from qudi.util.mutex import RecursiveMutex -from qudi.util.linear_transform import LinearTransformation # lives in branch qudi-core:coord-transforma +from qudi.util.linear_transform import LinearTransformation, LinearTransformation3D # lives in branch qudi-core:coord-transforma from qudi.core.connector import Connector from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar @@ -327,12 +327,17 @@ def toggle_scan(self, start, scan_axes, caller_id=None): return self.start_scan(scan_axes, caller_id) return self.stop_scan() - def toggle_tilt_correction(self, enable=True): + def toggle_tilt_correction(self, enable=True, debug_func=False): # todo: this function should be general for n axes depending on config of self._scan_axes # todo: should do a sensible rotation - coord_transform = LinearTransformation(dimensions=len(self.scanner_axes)) - func = coord_transform.rotate + if debug_func: + func = self.__func_debug_transform() + self.log.info("Set test functions for coord transform") + else: + coord_transform = LinearTransformation(dimensions=len(self.scanner_axes)) + func = coord_transform.rotate + if enable: self._scanner().set_coordinate_transform(func) else: @@ -345,6 +350,30 @@ def configure_tilt_correction(self, matrix=None, support_vecs=None): # 2. a reotation matrix pass + def __func_debug_transform(self): + def transform_to(coord, inverse=False): + # this is a stub function + if inverse: + return {key: 0.5 * val for key, val in coord.items()} + else: + return {key: 2 * val for key, val in coord.items()} + + def transform_to(coord, inverse=False): + + ax_2_idx = lambda ch: ord(ch) - 120 # x->0, y->1, z->2; todo only for these axes + transform = LinearTransformation3D() + + transform.rotate(0, 0, np.pi/10) + # todo: LinearTransformation expects vectors as row (not column) vectors + coord_vec = np.asarray(list(coord.values())).T + coord_transf = transform(coord_vec, invert=inverse).T + # make dict again after vector rotation + coord_transf = {ax: coord_transf[ax_2_idx(ax)] for (ax, val) in coord.items()} + + return coord_transf + + return transform_to + def _update_scan_settings(self, scan_axes, settings): for ax_index, ax in enumerate(scan_axes): # Update scan ranges if needed From ef6763b94f3eb705cdfdd7cbfd3bababe4c5e24e Mon Sep 17 00:00:00 2001 From: anjusha-vs Date: Wed, 12 Apr 2023 11:49:48 +0200 Subject: [PATCH 08/81] added the tilt_correction buttons --- src/qudi/gui/scanning/scannergui.py | 22 +++++++++++++++++++++- src/qudi/gui/scanning/ui_scannergui.ui | 11 ++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 9bbbc2b18..b280fcc52 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -34,7 +34,7 @@ from qudi.interface.scanning_probe_interface import ScanData from qudi.core.module import GuiBase from qudi.logic.scanning_optimize_logic import OptimizerScanSequence - +from qudi.gui.scanning.tilt_correction_dcokwidget import TiltCorrectionDockWidget from qudi.gui.scanning.axes_control_dockwidget import AxesControlDockWidget from qudi.gui.scanning.optimizer_setting_dialog import OptimizerSettingDialog from qudi.gui.scanning.scan_settings_dialog import ScannerSettingDialog @@ -63,6 +63,13 @@ def mouseDoubleClickEvent(self, event): super().mouseDoubleClickEvent(event) return + # Create the tilt correction dock widget + + #tilt_correction_dock_widget = TiltCorrectionDockWidget() + + # Add the dock widget to the main window + #self.addDockWidget(Qt.DockWidgetArea(8), tilt_correction_dock_widget) + class SaveDialog(QtWidgets.QDialog): """ Dialog to provide feedback and block GUI while saving """ def __init__(self, parent, title="Please wait", text="Saving..."): @@ -335,6 +342,7 @@ def _init_static_dockwidgets(self): self._mw.action_view_scanner_control.setChecked) self._mw.action_view_scanner_control.triggered[bool].connect( self.scanner_control_dockwidget.setVisible) + self._mw.action_view_line_scan.triggered[bool].connect( lambda is_vis: [wid.setVisible(is_vis) for wid in self.scan_1d_dockwidgets.values()] ) @@ -371,6 +379,14 @@ def _init_static_dockwidgets(self): self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) + self.tilt_correction_dockwidget = TiltCorrectionDockWidget() + self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.TopDockWidgetArea) + self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.tilt_correction_dockwidget) + self.tilt_correction_dockwidget.setVisible(False) + self._mw.action_view_Tilt_correction.triggered[bool].connect(self.tilt_correction_dockwidget.setVisible) + + + @QtCore.Slot() def restore_default_view(self): """ Restore the arrangement of DockWidgets to default """ @@ -554,6 +570,10 @@ def _add_scan_dockwidget(self, axes): self.__get_range_from_selection_func(axes) ) + def _add_tilt_correction_dock_widget(self): + dockwidget = TiltCorrectionDockWidget() + self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget) + def set_active_tab(self, axes): avail_axs = list(self.scan_1d_dockwidgets.keys()) avail_axs.extend(self.scan_2d_dockwidgets.keys()) diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index 8e29f575f..ba519dd75 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -33,7 +33,7 @@ 0 0 1440 - 30 + 18 @@ -64,6 +64,7 @@ + @@ -330,6 +331,14 @@ Shortcut:Alt+T Save all + + + true + + + Tilt_correction + + From d63643cccdc52ad4261686088f07f1cf48cf0476 Mon Sep 17 00:00:00 2001 From: anjusha-vs Date: Thu, 13 Apr 2023 18:15:08 +0200 Subject: [PATCH 09/81] all buttons for Tilt_correction are added --- src/qudi/gui/scanning/ui_scannergui.ui | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index ba519dd75..40bed47df 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -33,7 +33,7 @@ 0 0 1440 - 18 + 17 @@ -92,9 +92,9 @@ - + @@ -339,6 +339,18 @@ Shortcut:Alt+T Tilt_correction + + + true + + + + ../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg + + + Tilt_correction + + From da711d59f0696a30d7229fd2465fcbfcfb0ea792 Mon Sep 17 00:00:00 2001 From: anjusha-vs Date: Mon, 17 Apr 2023 11:30:41 +0200 Subject: [PATCH 10/81] tilt_correction_dockwidget --- .../scanning/tilt_correction_dcokwidget.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/qudi/gui/scanning/tilt_correction_dcokwidget.py diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py new file mode 100644 index 000000000..d1bd7a64b --- /dev/null +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +""" +This file contains a custom QWidget class to provide Tilt correction parameters and to calculate the Tilt. + +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__ = ('TiltCorrectionDockWidget') + + +from PySide2.QtWidgets import QDockWidget, QWidget,QGridLayout, QLabel, QPushButton,QTableWidget +from qudi.util.widgets.scientific_spinbox import ScienDSpinBox +#from qudi.interface.scanning_probe_interface import ScanData +#from qudi.gui.scanning.scan_dockwidget import ScanDockWidget + +class TiltCorrectionDockWidget(QDockWidget): + def __init__(self, parent=None): + super(TiltCorrectionDockWidget, self).__init__(parent) + self.setWindowTitle("Tilt Correction") + # Create the dock widget contents + dock_widget_contents = QWidget() + dock_widget_layout = QGridLayout(dock_widget_contents) + # Create the widgets for tilt correction + + tiltpoint_label = QLabel("Set Tiltpoint") + dock_widget_layout.addWidget(tiltpoint_label,0,0) + tiltpoint_label = QLabel("X") + dock_widget_layout.addWidget(tiltpoint_label,0,1) + tiltpoint_label = QLabel("Y") + dock_widget_layout.addWidget(tiltpoint_label, 0, 2) + tiltpoint_label = QLabel("Z") + dock_widget_layout.addWidget(tiltpoint_label, 0, 3) + tilt_set_01_pushButton = QPushButton("01") + tilt_set_01_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(tilt_set_01_pushButton,1,0) + + tilt_set_02_pushButton = QPushButton("02") + tilt_set_02_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(tilt_set_02_pushButton,2,0) + + tilt_set_03_pushButton = QPushButton("03") + tilt_set_03_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(tilt_set_03_pushButton,3,0) + + tilt_set_03_pushButton = QPushButton("04") + tilt_set_03_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(tilt_set_03_pushButton, 4, 0) + + x0_position = ScienDSpinBox() + dock_widget_layout.addWidget(x0_position, 1, 1) + x1_position = ScienDSpinBox() + dock_widget_layout.addWidget(x1_position, 2, 1) + x2_position = ScienDSpinBox() + dock_widget_layout.addWidget(x2_position, 3, 1) + x3_position = ScienDSpinBox() + dock_widget_layout.addWidget(x3_position, 4, 1) + + y0_position = ScienDSpinBox() + dock_widget_layout.addWidget(y0_position, 1, 2) + y1_position = ScienDSpinBox() + dock_widget_layout.addWidget(y1_position, 2, 2) + y2_position = ScienDSpinBox() + dock_widget_layout.addWidget(y2_position, 3, 2) + y3_position = ScienDSpinBox() + dock_widget_layout.addWidget(y3_position, 4, 2) + + z0_position = ScienDSpinBox() + dock_widget_layout.addWidget(z0_position, 1, 3) + z1_position = ScienDSpinBox() + dock_widget_layout.addWidget(z1_position, 2, 3) + z2_position = ScienDSpinBox() + dock_widget_layout.addWidget(z2_position, 3, 3) + z3_position = ScienDSpinBox() + dock_widget_layout.addWidget(z3_position, 4, 3) + + + calc_tilt_pushButton = QPushButton("Calc. Tilt") + dock_widget_layout.addWidget(calc_tilt_pushButton) + + # Set the dock widget contents + + dock_widget_contents.setLayout(dock_widget_layout) + self.setWidget(dock_widget_contents) + From 7c20132816ee80a166970252db9f25d589d16b5c Mon Sep 17 00:00:00 2001 From: anjusha-vs Date: Tue, 18 Apr 2023 10:52:07 +0200 Subject: [PATCH 11/81] toggle_switch for Tilt_correction --- .../scanning/Tilt_correction_toggle_switch.py | 30 +++++++++++++++++++ src/qudi/gui/scanning/scannergui.py | 18 ++++++++++- .../scanning/tilt_correction_dcokwidget.py | 5 ++-- src/qudi/gui/scanning/ui_scannergui.ui | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/qudi/gui/scanning/Tilt_correction_toggle_switch.py diff --git a/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py b/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py new file mode 100644 index 000000000..f2ce0215b --- /dev/null +++ b/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py @@ -0,0 +1,30 @@ +from PySide2.QtWidgets import QWidget, QLabel, QVBoxLayout +from PyQt5.QtCore import Qt +class ActivateTiltCorrection(QWidget): + def __init__(self): + super().__init__() + + # Set up the UI for the toggle switch + self.initUI() + + def initUI(self): + # Create a label for the toggle switch + self.label = QLabel("Tilt Correction: OFF") + #self.label.setAlignment(Qt.AlignCenter) + + # Create a vertical layout for the toggle switch + layout = QVBoxLayout() + layout.addWidget(self.label) + + # Set the layout for the widget + self.setLayout(layout) + + # Connect the widget to a slot for handling the toggle switch state + self.label.mousePressEvent = self.toggleSwitch + + def toggleSwitch(self, event): + # Toggle the state of the toggle switch + if self.label.text() == "Tilt Correction: OFF": + self.label.setText("Tilt Correction: ON") + else: + self.label.setText("Tilt Correction: OFF") diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index b280fcc52..f385eba5f 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -26,7 +26,8 @@ from typing import Union, Tuple from functools import partial from PySide2 import QtCore, QtGui, QtWidgets - +from PySide2.QtWidgets import QAction +#from PyQt5.QtWidgets import QAction import qudi.util.uic as uic from qudi.core.connector import Connector from qudi.core.statusvariable import StatusVar @@ -35,6 +36,7 @@ from qudi.core.module import GuiBase from qudi.logic.scanning_optimize_logic import OptimizerScanSequence from qudi.gui.scanning.tilt_correction_dcokwidget import TiltCorrectionDockWidget +from qudi.gui.scanning.Tilt_correction_toggle_switch import ActivateTiltCorrection from qudi.gui.scanning.axes_control_dockwidget import AxesControlDockWidget from qudi.gui.scanning.optimizer_setting_dialog import OptimizerSettingDialog from qudi.gui.scanning.scan_settings_dialog import ScannerSettingDialog @@ -375,6 +377,20 @@ def _init_static_dockwidgets(self): self._mw.action_view_optimizer.triggered[bool].connect( self.optimizer_dockwidget.setVisible) + toggle_switch = ActivateTiltCorrection() + action = QAction(self) + #toolbar.addWidget(toggle_switch) + # Add the action to the toolbar + + self._mw.util_toolBar.addWidget(toggle_switch) + # Connect the action to a slot for handling the toggle switch state + action.triggered.connect(toggle_switch.toggleSwitch) + #action.setDefaultWidget(toggle_switch) + self._mw.util_toolBar.addAction(action) + + # Add the MyDockWidget to the QToolBar + #toolbar.addWidget(dock_widget) + self._mw.util_toolBar.visibilityChanged.connect( self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index d1bd7a64b..8586566d7 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -15,7 +15,6 @@ 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 . """ @@ -89,8 +88,8 @@ def __init__(self, parent=None): dock_widget_layout.addWidget(z3_position, 4, 3) - calc_tilt_pushButton = QPushButton("Calc. Tilt") - dock_widget_layout.addWidget(calc_tilt_pushButton) + #calc_tilt_pushButton = QPushButton("Calc. Tilt") + #dock_widget_layout.addWidget(calc_tilt_pushButton) # Set the dock widget contents diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index 40bed47df..1583dd142 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -348,7 +348,7 @@ Shortcut:Alt+T ../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg - Tilt_correction + Activate_Tilt_correction From a6186b626fe413d48642aad3793386aa1f902a68 Mon Sep 17 00:00:00 2001 From: anjusha-vs Date: Tue, 18 Apr 2023 18:11:36 +0200 Subject: [PATCH 12/81] toggle_switch for Tilt_correction --- .../scanning/Tilt_correction_toggle_switch.py | 34 +++++++++++++------ src/qudi/gui/scanning/scannergui.py | 21 ++++++------ .../scanning/tilt_correction_dcokwidget.py | 19 +++++++---- src/qudi/gui/scanning/ui_scannergui.ui | 1 - src/qudi/logic/scanning_data_logic.py | 1 + 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py b/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py index f2ce0215b..16a168988 100644 --- a/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py +++ b/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py @@ -1,6 +1,11 @@ -from PySide2.QtWidgets import QWidget, QLabel, QVBoxLayout -from PyQt5.QtCore import Qt +from PySide2.QtWidgets import QWidget, QLabel, QVBoxLayout,QCheckBox +from PySide2.QtCore import Qt, Signal +from PySide2.QtGui import QPalette, QColor + + class ActivateTiltCorrection(QWidget): + stateChanged = Signal(bool) + def __init__(self): super().__init__() @@ -9,8 +14,11 @@ def __init__(self): def initUI(self): # Create a label for the toggle switch - self.label = QLabel("Tilt Correction: OFF") - #self.label.setAlignment(Qt.AlignCenter) + self.label = QLabel() + self.label.setAlignment(Qt.AlignCenter) + self.label.setStyleSheet("QLabel { background-color: red; color: white; }") + self.label.setFixedSize(100, 40) + self.label.setText("Tilt_correction:OFF") # Create a vertical layout for the toggle switch layout = QVBoxLayout() @@ -19,12 +27,16 @@ def initUI(self): # Set the layout for the widget self.setLayout(layout) - # Connect the widget to a slot for handling the toggle switch state - self.label.mousePressEvent = self.toggleSwitch - - def toggleSwitch(self, event): + def mousePressEvent(self, event): # Toggle the state of the toggle switch - if self.label.text() == "Tilt Correction: OFF": - self.label.setText("Tilt Correction: ON") + if self.label.text() == "Tilt_correction:OFF": + self.label.setText("Tilt_correction:ON") + self.label.setStyleSheet("QLabel { background-color: green; color: white; }") + self.stateChanged.emit(True) # Emit signal for state change else: - self.label.setText("Tilt Correction: OFF") + self.label.setText("Tilt_correction:OFF") + self.label.setStyleSheet("QLabel { background-color: red; color: white; }") + self.stateChanged.emit(False) # Emit signal for state change + + def isChecked(self): + return self.label.text() == "Tilt_correction:ON" \ No newline at end of file diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index f385eba5f..a5f62696f 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -42,6 +42,8 @@ from qudi.gui.scanning.scan_settings_dialog import ScannerSettingDialog from qudi.gui.scanning.scan_dockwidget import ScanDockWidget from qudi.gui.scanning.optimizer_dockwidget import OptimizerDockWidget +from qudi.util.widgets.toggle_switch import ToggleSwitch +from qudi.gui.switch.switch_state_widgets import SwitchRadioButtonWidget, ToggleSwitchWidget class ConfocalMainWindow(QtWidgets.QMainWindow): @@ -377,19 +379,16 @@ def _init_static_dockwidgets(self): self._mw.action_view_optimizer.triggered[bool].connect( self.optimizer_dockwidget.setVisible) - toggle_switch = ActivateTiltCorrection() - action = QAction(self) - #toolbar.addWidget(toggle_switch) - # Add the action to the toolbar + # Create a ToggleSwitchWidget + toggle_switch_widget = ToggleSwitchWidget(switch_states=('Tilt_Correction:OFF', 'Tilt_Correction:ON')) - self._mw.util_toolBar.addWidget(toggle_switch) - # Connect the action to a slot for handling the toggle switch state - action.triggered.connect(toggle_switch.toggleSwitch) - #action.setDefaultWidget(toggle_switch) - self._mw.util_toolBar.addAction(action) + # Set size policy for the ToggleSwitchWidget + toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + #toggle_switch_widget.setStyleSheet("QToolButton { height: 20px; width: 80px; }") + + # Add the widget to the toolbar as a button + self._mw.util_toolBar.addWidget(toggle_switch_widget ) - # Add the MyDockWidget to the QToolBar - #toolbar.addWidget(dock_widget) self._mw.util_toolBar.visibilityChanged.connect( self._mw.action_view_toolbar.setChecked) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 8586566d7..4ad99973a 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -21,9 +21,10 @@ __all__ = ('TiltCorrectionDockWidget') - +from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtWidgets import QDockWidget, QWidget,QGridLayout, QLabel, QPushButton,QTableWidget from qudi.util.widgets.scientific_spinbox import ScienDSpinBox +from qudi.gui.switch.switch_state_widgets import SwitchRadioButtonWidget, ToggleSwitchWidget #from qudi.interface.scanning_probe_interface import ScanData #from qudi.gui.scanning.scan_dockwidget import ScanDockWidget @@ -36,7 +37,7 @@ def __init__(self, parent=None): dock_widget_layout = QGridLayout(dock_widget_contents) # Create the widgets for tilt correction - tiltpoint_label = QLabel("Set Tiltpoint") + tiltpoint_label = QLabel("Support Vectors") dock_widget_layout.addWidget(tiltpoint_label,0,0) tiltpoint_label = QLabel("X") dock_widget_layout.addWidget(tiltpoint_label,0,1) @@ -44,19 +45,24 @@ def __init__(self, parent=None): dock_widget_layout.addWidget(tiltpoint_label, 0, 2) tiltpoint_label = QLabel("Z") dock_widget_layout.addWidget(tiltpoint_label, 0, 3) - tilt_set_01_pushButton = QPushButton("01") + tilt_set_01_pushButton = QPushButton("Vec 1") tilt_set_01_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(tilt_set_01_pushButton,1,0) - tilt_set_02_pushButton = QPushButton("02") + tilt_set_02_pushButton = QPushButton("Vec 2") tilt_set_02_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(tilt_set_02_pushButton,2,0) - tilt_set_03_pushButton = QPushButton("03") + tilt_set_03_pushButton = QPushButton("Vec 3") tilt_set_03_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(tilt_set_03_pushButton,3,0) - tilt_set_03_pushButton = QPushButton("04") + toggle_switch_widget = ToggleSwitchWidget(switch_states=('OFF', 'ON')) + # Set size policy for the ToggleSwitchWidget + toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + dock_widget_layout.addWidget(toggle_switch_widget,4,4) + + tilt_set_03_pushButton = QPushButton("Set Origin") tilt_set_03_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(tilt_set_03_pushButton, 4, 0) @@ -88,6 +94,7 @@ def __init__(self, parent=None): dock_widget_layout.addWidget(z3_position, 4, 3) + #calc_tilt_pushButton = QPushButton("Calc. Tilt") #dock_widget_layout.addWidget(calc_tilt_pushButton) diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index 1583dd142..78fafebce 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -94,7 +94,6 @@ - diff --git a/src/qudi/logic/scanning_data_logic.py b/src/qudi/logic/scanning_data_logic.py index 2dea420a2..e41d63486 100644 --- a/src/qudi/logic/scanning_data_logic.py +++ b/src/qudi/logic/scanning_data_logic.py @@ -40,6 +40,7 @@ from qudi.util.datastorage import ImageFormat, NpyDataStorage, TextDataStorage from qudi.util.units import ScaledFloat + from qudi.interface.scanning_probe_interface import ScanData From d41e744f2611463ad2c6e30e376c56e52a6b59ca Mon Sep 17 00:00:00 2001 From: geegee Date: Tue, 9 May 2023 12:21:15 +0200 Subject: [PATCH 13/81] Implemented configure tilt function --- src/qudi/logic/scanning_probe_logic.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 8f2200d82..2b237e529 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -30,7 +30,8 @@ from qudi.core.connector import Connector from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar - +from qudi.util.basis_transformations.basis_transformation \ + import compute_rotation_mat_rodriguez, compute_reduced_vectors class ScanningProbeLogic(LogicBase): """ @@ -348,7 +349,14 @@ def configure_tilt_correction(self, matrix=None, support_vecs=None): # 1. a) single support vector defining z plane # b) 3 more support vectors defining a set of planes # 2. a reotation matrix - pass + red_support_vecs = compute_reduced_vectors(support_vecs) + rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) + shift = red_support_vecs[3] + lin_transform = LinearTransformation() + lin_transform.rotate(rot_mat) + lin_transform.translate(shift) + + return lin_transform.__call__ def __func_debug_transform(self): def transform_to(coord, inverse=False): From e65b74188375bce6960bff3f353a4d81b43e8518 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 15 May 2023 15:03:40 +0200 Subject: [PATCH 14/81] make configure_tilt_correction() working --- src/qudi/logic/scanning_probe_logic.py | 38 ++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 2b237e529..796bd341b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -78,7 +78,7 @@ def __init__(self, *args, **kwargs): self.__scan_poll_interval = 0 self.__scan_stop_requested = True self._curr_caller_id = self.module_uuid - return + self._tilt_corr_transform = None def on_activate(self): """ Initialisation performed during activation of the module. @@ -336,27 +336,37 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): func = self.__func_debug_transform() self.log.info("Set test functions for coord transform") else: - coord_transform = LinearTransformation(dimensions=len(self.scanner_axes)) - func = coord_transform.rotate + func = self._tilt_corr_transform.__call__ if enable: self._scanner().set_coordinate_transform(func) else: self._scanner().set_coordinate_transform(None) - def configure_tilt_correction(self, matrix=None, support_vecs=None): - # todo: should calculate the needed transformation from either - # 1. a) single support vector defining z plane - # b) 3 more support vectors defining a set of planes - # 2. a reotation matrix - red_support_vecs = compute_reduced_vectors(support_vecs) + def configure_tilt_correction(self, support_vecs=None, shift_vec=None): + + support_vecs = np.asarray(support_vecs) + + if support_vecs.shape[0] != 3: + raise ValueError(f"Need 3 n-dim support vectors, not {red_support_vecs.shape[0]}") + + if shift_vec is None: + red_support_vecs = compute_reduced_vectors(support_vecs) + shift_vec = np.mean(red_support_vecs, axis=0) + else: + shift_vec = np.asarray(shift_vec) + red_support_vecs = compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) + shift_vec = red_support_vecs[-1,:] + rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) - shift = red_support_vecs[3] - lin_transform = LinearTransformation() - lin_transform.rotate(rot_mat) - lin_transform.translate(shift) + shift = shift_vec + + lin_transform = LinearTransformation3D() + lin_transform.add_rotation(rot_mat) + lin_transform.translate(shift[0], shift[1], shift[2]) + + self._tilt_corr_transform = lin_transform - return lin_transform.__call__ def __func_debug_transform(self): def transform_to(coord, inverse=False): From c59ca86d55b670f361cd9ac866041b6a87f22be3 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 15 May 2023 15:45:36 +0200 Subject: [PATCH 15/81] small fix --- src/qudi/logic/scanning_probe_logic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 796bd341b..de5bce579 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -329,14 +329,12 @@ def toggle_scan(self, start, scan_axes, caller_id=None): return self.stop_scan() def toggle_tilt_correction(self, enable=True, debug_func=False): - # todo: this function should be general for n axes depending on config of self._scan_axes - # todo: should do a sensible rotation if debug_func: func = self.__func_debug_transform() self.log.info("Set test functions for coord transform") else: - func = self._tilt_corr_transform.__call__ + func = self._tilt_corr_transform.__call__ if self._tilt_corr_transform else None if enable: self._scanner().set_coordinate_transform(func) From c6b122735576c8db76a8515772a40543895de5aa Mon Sep 17 00:00:00 2001 From: timoML Date: Thu, 18 May 2023 00:11:54 +0200 Subject: [PATCH 16/81] connect gui to configure_tilt_correction --- src/qudi/gui/scanning/scannergui.py | 32 +++++++ .../scanning/tilt_correction_dcokwidget.py | 96 +++++++++++-------- src/qudi/logic/scanning_probe_logic.py | 2 +- 3 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index a5f62696f..5843408cb 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -239,6 +239,18 @@ def on_activate(self): self.sigShowSaveDialog.connect(lambda x: self._save_dialog.show() if x else self._save_dialog.hide(), QtCore.Qt.DirectConnection) + # tilt correction signals + tilt_widget = self.tilt_correction_dockwidget + tilt_widget.tilt_set_01_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(0), + QtCore.Qt.QueuedConnection) + tilt_widget.tilt_set_02_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(1), + QtCore.Qt.QueuedConnection) + tilt_widget.tilt_set_03_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(2), + QtCore.Qt.QueuedConnection) + tilt_widget.tilt_set_04_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(3), + QtCore.Qt.QueuedConnection) + + # Initialize dockwidgets to default view self.restore_default_view() self.show() @@ -1017,3 +1029,23 @@ def update_optimizer_settings(self, settings=None): # Adjust crosshair size according to optimizer range self.update_crosshair_sizes() + + def tilt_corr_support_vector_updated(self, idx_vector=0): + target = self._scanning_logic().scanner_target + + dim_idxs = [(idx, key) for idx,key in enumerate(target.keys())] + support_vecs = self.tilt_correction_dockwidget.support_vecs_box + [support_vecs[idx_vector][dim[0]].setValue(target[dim[1]]) for dim in dim_idxs] + + self.log.debug(f"Button {idx_vector} clicked, current target {target}") + + support_vecs_val = self.tilt_correction_dockwidget.support_vectors + all_vecs_valid = True + for vec in [0,1,2,3]: + vecs_valid = [support_vecs[vec][dim[0]].is_valid for dim in dim_idxs] + all_vecs_valid = np.all(vecs_valid) and all_vecs_valid + + if all_vecs_valid: + self._scanning_logic().configure_tilt_correction(np.asarray(support_vecs_val[:-1]).T, + np.asarray(support_vecs_val[-1]).T) + diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 4ad99973a..469d5cf43 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -21,6 +21,8 @@ __all__ = ('TiltCorrectionDockWidget') +import numpy as np + from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtWidgets import QDockWidget, QWidget,QGridLayout, QLabel, QPushButton,QTableWidget from qudi.util.widgets.scientific_spinbox import ScienDSpinBox @@ -29,8 +31,11 @@ #from qudi.gui.scanning.scan_dockwidget import ScanDockWidget class TiltCorrectionDockWidget(QDockWidget): - def __init__(self, parent=None): + def __init__(self, parent=None, n_dim=3): super(TiltCorrectionDockWidget, self).__init__(parent) + + self._n_dim = n_dim + self.setWindowTitle("Tilt Correction") # Create the dock widget contents dock_widget_contents = QWidget() @@ -45,53 +50,39 @@ def __init__(self, parent=None): dock_widget_layout.addWidget(tiltpoint_label, 0, 2) tiltpoint_label = QLabel("Z") dock_widget_layout.addWidget(tiltpoint_label, 0, 3) - tilt_set_01_pushButton = QPushButton("Vec 1") - tilt_set_01_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(tilt_set_01_pushButton,1,0) + self.tilt_set_01_pushButton = QPushButton("Vec 1") + self.tilt_set_01_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(self.tilt_set_01_pushButton,1,0) - tilt_set_02_pushButton = QPushButton("Vec 2") - tilt_set_02_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(tilt_set_02_pushButton,2,0) + self.tilt_set_02_pushButton = QPushButton("Vec 2") + self.tilt_set_02_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(self.tilt_set_02_pushButton,2,0) - tilt_set_03_pushButton = QPushButton("Vec 3") - tilt_set_03_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(tilt_set_03_pushButton,3,0) + self.tilt_set_03_pushButton = QPushButton("Vec 3") + self.tilt_set_03_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(self.tilt_set_03_pushButton,3,0) + origin_switch_label = QLabel("Auto origin") + dock_widget_layout.addWidget(origin_switch_label, 4, 0) toggle_switch_widget = ToggleSwitchWidget(switch_states=('OFF', 'ON')) # Set size policy for the ToggleSwitchWidget toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - dock_widget_layout.addWidget(toggle_switch_widget,4,4) - - tilt_set_03_pushButton = QPushButton("Set Origin") - tilt_set_03_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(tilt_set_03_pushButton, 4, 0) - - x0_position = ScienDSpinBox() - dock_widget_layout.addWidget(x0_position, 1, 1) - x1_position = ScienDSpinBox() - dock_widget_layout.addWidget(x1_position, 2, 1) - x2_position = ScienDSpinBox() - dock_widget_layout.addWidget(x2_position, 3, 1) - x3_position = ScienDSpinBox() - dock_widget_layout.addWidget(x3_position, 4, 1) - - y0_position = ScienDSpinBox() - dock_widget_layout.addWidget(y0_position, 1, 2) - y1_position = ScienDSpinBox() - dock_widget_layout.addWidget(y1_position, 2, 2) - y2_position = ScienDSpinBox() - dock_widget_layout.addWidget(y2_position, 3, 2) - y3_position = ScienDSpinBox() - dock_widget_layout.addWidget(y3_position, 4, 2) - - z0_position = ScienDSpinBox() - dock_widget_layout.addWidget(z0_position, 1, 3) - z1_position = ScienDSpinBox() - dock_widget_layout.addWidget(z1_position, 2, 3) - z2_position = ScienDSpinBox() - dock_widget_layout.addWidget(z2_position, 3, 3) - z3_position = ScienDSpinBox() - dock_widget_layout.addWidget(z3_position, 4, 3) + dock_widget_layout.addWidget(toggle_switch_widget,4,1) + + self.tilt_set_04_pushButton = QPushButton("Origin") + self.tilt_set_04_pushButton.setMaximumSize(70, 16777215) + dock_widget_layout.addWidget(self.tilt_set_04_pushButton, 5, 0) + + self.support_vecs_box = [] # row: idx of support vecs (1-4), col: dimension (0-n) + for idx_row in [1, 2, 3, 5]: + pos_vecs = [] + for idx_dim in range(0, n_dim): + x_i_position = ScienDSpinBox() + dock_widget_layout.addWidget(x_i_position, idx_row, idx_dim+1) + x_i_position.setValue(np.nan) + pos_vecs.append(x_i_position) + self.support_vecs_box.append(pos_vecs) + @@ -103,3 +94,24 @@ def __init__(self, parent=None): dock_widget_contents.setLayout(dock_widget_layout) self.setWidget(dock_widget_contents) + @property + def support_vectors(self): + support_vecs = self.support_vecs_box + + vec_vals = [] + for vec in support_vecs: + vec_vals.append([box.value() for box in vec]) + + return vec_vals + """ + dim_idxs = list(range(self._n_dim)) + + all_vecs_valid = True + for vec in [0, 1, 2, 3]: + vecs_valid = [support_vecs[vec][dim].is_valid for dim in dim_idxs] + all_vecs_valid = np.all(vecs_valid) and all_vecs_valid + + """ + + + diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index de5bce579..74ace0b1f 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -346,7 +346,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): support_vecs = np.asarray(support_vecs) if support_vecs.shape[0] != 3: - raise ValueError(f"Need 3 n-dim support vectors, not {red_support_vecs.shape[0]}") + raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") if shift_vec is None: red_support_vecs = compute_reduced_vectors(support_vecs) From 7be6249ea297f4d170cc108e430727dbb1ebdcc5 Mon Sep 17 00:00:00 2001 From: timoML Date: Thu, 18 May 2023 00:36:46 +0200 Subject: [PATCH 17/81] disable currently broken dim reduction of support vectors --- src/qudi/gui/scanning/scannergui.py | 5 +++-- src/qudi/logic/scanning_probe_logic.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 5843408cb..ea3095dfa 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1046,6 +1046,7 @@ def tilt_corr_support_vector_updated(self, idx_vector=0): all_vecs_valid = np.all(vecs_valid) and all_vecs_valid if all_vecs_valid: - self._scanning_logic().configure_tilt_correction(np.asarray(support_vecs_val[:-1]).T, - np.asarray(support_vecs_val[-1]).T) + self._scanning_logic().configure_tilt_correction(np.asarray(support_vecs_val[:-1]), + np.asarray(support_vecs_val[-1])) + diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 74ace0b1f..550a4aa27 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -349,11 +349,12 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") if shift_vec is None: - red_support_vecs = compute_reduced_vectors(support_vecs) + # todo: for the case of 3d vectors in plane, throws out too many dimensions + red_support_vecs = support_vecs#compute_reduced_vectors(support_vecs) shift_vec = np.mean(red_support_vecs, axis=0) else: shift_vec = np.asarray(shift_vec) - red_support_vecs = compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) + red_support_vecs = np.vstack([support_vecs, shift_vec])# compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) shift_vec = red_support_vecs[-1,:] rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) From a7595c58efc957f9d991c59c9297df8e0cdc9dfb Mon Sep 17 00:00:00 2001 From: timoML Date: Thu, 18 May 2023 09:34:25 +0200 Subject: [PATCH 18/81] connect 'auto origin' button --- src/qudi/gui/scanning/scannergui.py | 6 +++++ .../scanning/tilt_correction_dcokwidget.py | 25 +++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index ea3095dfa..d02bdb74c 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -300,6 +300,12 @@ def on_deactivate(self): for scan in tuple(self.scan_2d_dockwidgets): self._remove_scan_dockwidget(scan) + tilt_widget = self.tilt_correction_dockwidget + tilt_widget.tilt_set_01_pushButton.clicked.disconnect() + tilt_widget.tilt_set_02_pushButton.clicked.disconnect() + tilt_widget.tilt_set_03_pushButton.clicked.disconnect() + tilt_widget.tilt_set_04_pushButton.clicked.disconnect() + def show(self): """Make main window visible and put it above all other windows. """ # Show the Main Confocal GUI: diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 469d5cf43..82fd46e58 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -64,10 +64,12 @@ def __init__(self, parent=None, n_dim=3): origin_switch_label = QLabel("Auto origin") dock_widget_layout.addWidget(origin_switch_label, 4, 0) - toggle_switch_widget = ToggleSwitchWidget(switch_states=('OFF', 'ON')) + toggle_auto_or_widget = ToggleSwitchWidget(switch_states=('OFF', 'ON')) + toggle_auto_or_widget.toggle_switch.sigStateChanged.connect(self.auto_origin_changed, + QtCore.Qt.QueuedConnection) # Set size policy for the ToggleSwitchWidget - toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - dock_widget_layout.addWidget(toggle_switch_widget,4,1) + toggle_auto_or_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + dock_widget_layout.addWidget(toggle_auto_or_widget,4,1) self.tilt_set_04_pushButton = QPushButton("Origin") self.tilt_set_04_pushButton.setMaximumSize(70, 16777215) @@ -94,6 +96,8 @@ def __init__(self, parent=None, n_dim=3): dock_widget_contents.setLayout(dock_widget_layout) self.setWidget(dock_widget_contents) + # todo: disconnect toggle_auto_or_widget.toggle_switch.sigStateChanged + @property def support_vectors(self): support_vecs = self.support_vecs_box @@ -103,15 +107,16 @@ def support_vectors(self): vec_vals.append([box.value() for box in vec]) return vec_vals - """ - dim_idxs = list(range(self._n_dim)) - all_vecs_valid = True - for vec in [0, 1, 2, 3]: - vecs_valid = [support_vecs[vec][dim].is_valid for dim in dim_idxs] - all_vecs_valid = np.all(vecs_valid) and all_vecs_valid + def auto_origin_changed(self, state): + # todo: make sure shift vector is correctly returned in this case + + auto_enabled = True if state == 'ON' else False + + [el.setEnabled(not auto_enabled) for el in self.support_vecs_box[-1]] + self.tilt_set_04_pushButton.setEnabled(not auto_enabled) + - """ From 03b2b75b561379c1f59fd6fdbed7290fe8133868 Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 11:21:29 +0200 Subject: [PATCH 19/81] make gui configuration of tilt correction functional --- src/qudi/gui/scanning/scannergui.py | 51 ++++++++++++++----- .../scanning/tilt_correction_dcokwidget.py | 47 ++++++++++------- src/qudi/logic/scanning_probe_logic.py | 4 ++ 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index d02bdb74c..92d87fdea 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -241,15 +241,19 @@ def on_activate(self): # tilt correction signals tilt_widget = self.tilt_correction_dockwidget - tilt_widget.tilt_set_01_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(0), + tilt_widget.tilt_set_01_pushButton.clicked.connect(lambda: self.tilt_corr_set_support_vector(0), QtCore.Qt.QueuedConnection) - tilt_widget.tilt_set_02_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(1), + tilt_widget.tilt_set_02_pushButton.clicked.connect(lambda: self.tilt_corr_set_support_vector(1), QtCore.Qt.QueuedConnection) - tilt_widget.tilt_set_03_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(2), + tilt_widget.tilt_set_03_pushButton.clicked.connect(lambda: self.tilt_corr_set_support_vector(2), QtCore.Qt.QueuedConnection) - tilt_widget.tilt_set_04_pushButton.clicked.connect(lambda: self.tilt_corr_support_vector_updated(3), + tilt_widget.tilt_set_04_pushButton.clicked.connect(lambda: self.tilt_corr_set_support_vector(3), + QtCore.Qt.QueuedConnection) + tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) + self.toggle_switch_widget.toggle_switch.sigStateChanged.connect(self.toggle_tilt_correction, + QtCore.Qt.QueuedConnection) # Initialize dockwidgets to default view self.restore_default_view() @@ -300,11 +304,13 @@ def on_deactivate(self): for scan in tuple(self.scan_2d_dockwidgets): self._remove_scan_dockwidget(scan) + self.toggle_switch_widget.toggle_switch.sigStateChanged.disconnect() tilt_widget = self.tilt_correction_dockwidget tilt_widget.tilt_set_01_pushButton.clicked.disconnect() tilt_widget.tilt_set_02_pushButton.clicked.disconnect() tilt_widget.tilt_set_03_pushButton.clicked.disconnect() tilt_widget.tilt_set_04_pushButton.clicked.disconnect() + tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.disconnect() def show(self): """Make main window visible and put it above all other windows. """ @@ -398,23 +404,23 @@ def _init_static_dockwidgets(self): self.optimizer_dockwidget.setVisible) # Create a ToggleSwitchWidget - toggle_switch_widget = ToggleSwitchWidget(switch_states=('Tilt_Correction:OFF', 'Tilt_Correction:ON')) + self.toggle_switch_widget = ToggleSwitchWidget(switch_states=('Tilt_Correction:OFF', + 'Tilt_Correction:ON')) # Set size policy for the ToggleSwitchWidget - toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + self.toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) #toggle_switch_widget.setStyleSheet("QToolButton { height: 20px; width: 80px; }") # Add the widget to the toolbar as a button - self._mw.util_toolBar.addWidget(toggle_switch_widget ) - + self._mw.util_toolBar.addWidget(self.toggle_switch_widget) self._mw.util_toolBar.visibilityChanged.connect( self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) self.tilt_correction_dockwidget = TiltCorrectionDockWidget() - self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.TopDockWidgetArea) - self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.tilt_correction_dockwidget) + self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) + self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.tilt_correction_dockwidget) self.tilt_correction_dockwidget.setVisible(False) self._mw.action_view_Tilt_correction.triggered[bool].connect(self.tilt_correction_dockwidget.setVisible) @@ -1036,23 +1042,40 @@ def update_optimizer_settings(self, settings=None): # Adjust crosshair size according to optimizer range self.update_crosshair_sizes() - def tilt_corr_support_vector_updated(self, idx_vector=0): + def tilt_corr_set_support_vector(self, idx_vector=0): target = self._scanning_logic().scanner_target dim_idxs = [(idx, key) for idx,key in enumerate(target.keys())] support_vecs = self.tilt_correction_dockwidget.support_vecs_box + [support_vecs[idx_vector][dim[0]].setValue(target[dim[1]]) for dim in dim_idxs] - self.log.debug(f"Button {idx_vector} clicked, current target {target}") + self.tilt_corr_support_vector_updated() + + def tilt_corr_support_vector_updated(self): + support_vecs = self.tilt_correction_dockwidget.support_vecs_box support_vecs_val = self.tilt_correction_dockwidget.support_vectors + dim_idxs = [(idx, key) for idx, key in enumerate(self._scanning_logic().scanner_axes.keys())] + all_vecs_valid = True for vec in [0,1,2,3]: vecs_valid = [support_vecs[vec][dim[0]].is_valid for dim in dim_idxs] all_vecs_valid = np.all(vecs_valid) and all_vecs_valid if all_vecs_valid: - self._scanning_logic().configure_tilt_correction(np.asarray(support_vecs_val[:-1]), - np.asarray(support_vecs_val[-1])) + shift_vec = support_vecs_val[-1] + if not np.all([np.isfinite(el) for el in shift_vec]): + shift_vec = None + self._scanning_logic().configure_tilt_correction(support_vecs_val[:-1], + shift_vec) + self.toggle_switch_widget.setEnabled(True) + else: + self._scanning_logic().configure_tilt_correction(None, None) + self.toggle_switch_widget.set_state('Tilt_Correction:OFF') + self.toggle_switch_widget.setEnabled(False) + def toggle_tilt_correction(self, state): + enabled = True if state == 'Tilt_Correction:ON' else False + self._scanning_logic().toggle_tilt_correction(enabled) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 82fd46e58..73117cb37 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -43,33 +43,33 @@ def __init__(self, parent=None, n_dim=3): # Create the widgets for tilt correction tiltpoint_label = QLabel("Support Vectors") - dock_widget_layout.addWidget(tiltpoint_label,0,0) + dock_widget_layout.addWidget(tiltpoint_label, 0, 0) tiltpoint_label = QLabel("X") - dock_widget_layout.addWidget(tiltpoint_label,0,1) + dock_widget_layout.addWidget(tiltpoint_label, 0, 1) tiltpoint_label = QLabel("Y") dock_widget_layout.addWidget(tiltpoint_label, 0, 2) tiltpoint_label = QLabel("Z") dock_widget_layout.addWidget(tiltpoint_label, 0, 3) self.tilt_set_01_pushButton = QPushButton("Vec 1") self.tilt_set_01_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(self.tilt_set_01_pushButton,1,0) + dock_widget_layout.addWidget(self.tilt_set_01_pushButton, 1, 0) self.tilt_set_02_pushButton = QPushButton("Vec 2") self.tilt_set_02_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(self.tilt_set_02_pushButton,2,0) + dock_widget_layout.addWidget(self.tilt_set_02_pushButton, 2, 0) self.tilt_set_03_pushButton = QPushButton("Vec 3") self.tilt_set_03_pushButton.setMaximumSize(70, 16777215) - dock_widget_layout.addWidget(self.tilt_set_03_pushButton,3,0) + dock_widget_layout.addWidget(self.tilt_set_03_pushButton, 3, 0) origin_switch_label = QLabel("Auto origin") dock_widget_layout.addWidget(origin_switch_label, 4, 0) - toggle_auto_or_widget = ToggleSwitchWidget(switch_states=('OFF', 'ON')) - toggle_auto_or_widget.toggle_switch.sigStateChanged.connect(self.auto_origin_changed, + self.auto_origin_switch = ToggleSwitchWidget(switch_states=('OFF', 'ON')) + # todo: disconnect self.auto_origin_switch.toggle_switch.sigStateChanged + self.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.auto_origin_changed, QtCore.Qt.QueuedConnection) - # Set size policy for the ToggleSwitchWidget - toggle_auto_or_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - dock_widget_layout.addWidget(toggle_auto_or_widget,4,1) + self.auto_origin_switch.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + dock_widget_layout.addWidget(self.auto_origin_switch, 4, 1) self.tilt_set_04_pushButton = QPushButton("Origin") self.tilt_set_04_pushButton.setMaximumSize(70, 16777215) @@ -80,23 +80,19 @@ def __init__(self, parent=None, n_dim=3): pos_vecs = [] for idx_dim in range(0, n_dim): x_i_position = ScienDSpinBox() + x_i_position.setMinimumWidth(70) dock_widget_layout.addWidget(x_i_position, idx_row, idx_dim+1) x_i_position.setValue(np.nan) pos_vecs.append(x_i_position) self.support_vecs_box.append(pos_vecs) - - - - #calc_tilt_pushButton = QPushButton("Calc. Tilt") - #dock_widget_layout.addWidget(calc_tilt_pushButton) - # Set the dock widget contents - dock_widget_contents.setLayout(dock_widget_layout) self.setWidget(dock_widget_contents) - # todo: disconnect toggle_auto_or_widget.toggle_switch.sigStateChanged + # default init auto origin button = True + self.auto_origin_switch.set_state('ON') + self.auto_origin_changed(self.auto_origin_switch.switch_state) @property def support_vectors(self): @@ -108,15 +104,28 @@ def support_vectors(self): return vec_vals + @property + def auto_origin(self): + return True if self.auto_origin_switch.switch_state == 'ON' else False + def auto_origin_changed(self, state): - # todo: make sure shift vector is correctly returned in this case auto_enabled = True if state == 'ON' else False [el.setEnabled(not auto_enabled) for el in self.support_vecs_box[-1]] + + # nan renders the gui boxes invalid/red, so instead inf + [el.setValue(np.inf) for el in self.support_vecs_box[-1]] + if not auto_enabled: + [el.setValue(np.nan) for el in self.support_vecs_box[-1]] + self.tilt_set_04_pushButton.setEnabled(not auto_enabled) + + + + diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 550a4aa27..875d4008c 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -343,6 +343,10 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): def configure_tilt_correction(self, support_vecs=None, shift_vec=None): + if support_vecs is None: + self._tilt_corr_transform = None + return + support_vecs = np.asarray(support_vecs) if support_vecs.shape[0] != 3: From e6e797d5c39d68c023c036dedfce5649bcf7efbb Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 11:59:42 +0200 Subject: [PATCH 20/81] fix translation between dict-like and vector coordinates --- src/qudi/logic/scanning_probe_logic.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 875d4008c..7a359f13b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -334,7 +334,7 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): func = self.__func_debug_transform() self.log.info("Set test functions for coord transform") else: - func = self._tilt_corr_transform.__call__ if self._tilt_corr_transform else None + func = self.__transform_func if self._tilt_corr_transform else None if enable: self._scanner().set_coordinate_transform(func) @@ -478,6 +478,7 @@ def stop_scan(self): return err + def __scan_poll_loop(self): with self._thread_lock: try: @@ -517,3 +518,14 @@ def __stop_timer(self): QtCore.Qt.BlockingQueuedConnection) else: self.__scan_poll_timer.stop() + + def __transform_func(coord, inverse=False): + # convert from coordinate dict to plain vector + ax_2_idx = lambda ch: ord(ch) - 120 # x->0, y->1, z->2; todo only for these axes + transform = self._tilt_corr_transform.__call__ + coord_vec = np.asarray(list(coord.values())).T + coord_transf = transform(coord_vec, invert=inverse).T + # make dict again after vector rotation + coord_transf = {ax: coord_transf[ax_2_idx(ax)] for (ax, val) in coord.items()} + + return coord_transf From 9e862e8ac3359411f6682c5b532d3d080503aafd Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 19:19:20 +0200 Subject: [PATCH 21/81] add toggle_tilt_correction as toolbar button --- src/qudi/gui/scanning/scannergui.py | 26 +++++++++++++++++++++++--- src/qudi/gui/scanning/ui_scannergui.ui | 15 +++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 92d87fdea..363855f7e 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -251,9 +251,12 @@ def on_activate(self): QtCore.Qt.QueuedConnection) tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) - self.toggle_switch_widget.toggle_switch.sigStateChanged.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) + self._mw.action_toggle_tilt_correction.triggered.connect(self.toggle_tilt_correction, + QtCore.Qt.QueuedConnection) + + self.tilt_corr_support_vector_updated() # Initialize dockwidgets to default view self.restore_default_view() @@ -311,6 +314,7 @@ def on_deactivate(self): tilt_widget.tilt_set_03_pushButton.clicked.disconnect() tilt_widget.tilt_set_04_pushButton.clicked.disconnect() tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.disconnect() + self._mw.action_toggle_tilt_correction.triggered.disconnect() def show(self): """Make main window visible and put it above all other windows. """ @@ -422,7 +426,7 @@ def _init_static_dockwidgets(self): self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.tilt_correction_dockwidget) self.tilt_correction_dockwidget.setVisible(False) - self._mw.action_view_Tilt_correction.triggered[bool].connect(self.tilt_correction_dockwidget.setVisible) + self._mw.action_view_tilt_correction.triggered[bool].connect(self.tilt_correction_dockwidget.setVisible) @@ -1070,12 +1074,28 @@ def tilt_corr_support_vector_updated(self): self._scanning_logic().configure_tilt_correction(support_vecs_val[:-1], shift_vec) self.toggle_switch_widget.setEnabled(True) + self._mw.action_toggle_tilt_correction.setEnabled(True) else: self._scanning_logic().configure_tilt_correction(None, None) self.toggle_switch_widget.set_state('Tilt_Correction:OFF') self.toggle_switch_widget.setEnabled(False) + self._mw.action_toggle_tilt_correction.setEnabled(False) def toggle_tilt_correction(self, state): - enabled = True if state == 'Tilt_Correction:ON' else False + # toggle switch + if type(state) == str: + enabled = True if state == 'Tilt_Correction:ON' else False + # button + if type(state) == bool: + enabled = state + self._scanning_logic().toggle_tilt_correction(enabled) + icon_on = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt.svg")) + icon_off = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_off.svg")) + if enabled: + self._mw.action_toggle_tilt_correction.setIcon(icon_on) + else: + self._mw.action_toggle_tilt_correction.setIcon(icon_off) + + diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index 78fafebce..eb4ed21d7 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -60,11 +60,12 @@ + - + @@ -94,6 +95,7 @@ + @@ -330,24 +332,25 @@ Shortcut:Alt+T Save all - + true - Tilt_correction + Tilt correction - + true - ../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt.svg + ../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt_off.svg + - Activate_Tilt_correction + Tilt correction From 18b2204cdd097e5c4ac643cedabe3703f0a26b44 Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 20:01:12 +0200 Subject: [PATCH 22/81] allow arbitrary dimensions of tilt correction --- src/qudi/gui/scanning/scannergui.py | 2 +- .../gui/scanning/tilt_correction_dcokwidget.py | 17 ++++++++--------- src/qudi/logic/scanning_probe_logic.py | 8 +++++++- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 363855f7e..43f599caf 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -422,7 +422,7 @@ def _init_static_dockwidgets(self): self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) - self.tilt_correction_dockwidget = TiltCorrectionDockWidget() + self.tilt_correction_dockwidget = TiltCorrectionDockWidget(scanner_axes=self._scanning_logic().scanner_axes) self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.tilt_correction_dockwidget) self.tilt_correction_dockwidget.setVisible(False) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 73117cb37..9711f6978 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -31,10 +31,10 @@ #from qudi.gui.scanning.scan_dockwidget import ScanDockWidget class TiltCorrectionDockWidget(QDockWidget): - def __init__(self, parent=None, n_dim=3): + def __init__(self, parent=None, scanner_axes=None): super(TiltCorrectionDockWidget, self).__init__(parent) - self._n_dim = n_dim + self._n_dim = len(scanner_axes) self.setWindowTitle("Tilt Correction") # Create the dock widget contents @@ -44,12 +44,11 @@ def __init__(self, parent=None, n_dim=3): tiltpoint_label = QLabel("Support Vectors") dock_widget_layout.addWidget(tiltpoint_label, 0, 0) - tiltpoint_label = QLabel("X") - dock_widget_layout.addWidget(tiltpoint_label, 0, 1) - tiltpoint_label = QLabel("Y") - dock_widget_layout.addWidget(tiltpoint_label, 0, 2) - tiltpoint_label = QLabel("Z") - dock_widget_layout.addWidget(tiltpoint_label, 0, 3) + + for idx, ax in enumerate(list(scanner_axes.keys())): + tiltpoint_label = QLabel(ax) + dock_widget_layout.addWidget(tiltpoint_label, 0, 1+idx) + self.tilt_set_01_pushButton = QPushButton("Vec 1") self.tilt_set_01_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(self.tilt_set_01_pushButton, 1, 0) @@ -78,7 +77,7 @@ def __init__(self, parent=None, n_dim=3): self.support_vecs_box = [] # row: idx of support vecs (1-4), col: dimension (0-n) for idx_row in [1, 2, 3, 5]: pos_vecs = [] - for idx_dim in range(0, n_dim): + for idx_dim in range(0, self._n_dim): x_i_position = ScienDSpinBox() x_i_position.setMinimumWidth(70) dock_widget_layout.addWidget(x_i_position, idx_row, idx_dim+1) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 7a359f13b..bd163b38b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -348,19 +348,25 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): return support_vecs = np.asarray(support_vecs) + ndim = len(self.scanner_axes) if support_vecs.shape[0] != 3: raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") if shift_vec is None: # todo: for the case of 3d vectors in plane, throws out too many dimensions - red_support_vecs = support_vecs#compute_reduced_vectors(support_vecs) + red_support_vecs = support_vecs #compute_reduced_vectors(support_vecs) shift_vec = np.mean(red_support_vecs, axis=0) else: shift_vec = np.asarray(shift_vec) red_support_vecs = np.vstack([support_vecs, shift_vec])# compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) shift_vec = red_support_vecs[-1,:] + # todo remove workaround code for dim reduction + red_support_vecs = red_support_vecs[:,:ndim-1] + shift_vec = shift_vec[:ndim-1] + + rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec From 9611709ad029f95305c91ba6ee54be2939e9183d Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 20:08:50 +0200 Subject: [PATCH 23/81] small fix --- src/qudi/logic/scanning_probe_logic.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index bd163b38b..ee2260b3b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -348,7 +348,6 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): return support_vecs = np.asarray(support_vecs) - ndim = len(self.scanner_axes) if support_vecs.shape[0] != 3: raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") @@ -363,9 +362,8 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): shift_vec = red_support_vecs[-1,:] # todo remove workaround code for dim reduction - red_support_vecs = red_support_vecs[:,:ndim-1] - shift_vec = shift_vec[:ndim-1] - + red_support_vecs = red_support_vecs[:,:3] + shift_vec = shift_vec[:3] rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec From 9232e32c1376ab4d98a08d45cf46c1c59f30f17f Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 19 May 2023 21:12:07 +0200 Subject: [PATCH 24/81] fix __transform_func for >3 dimensions --- src/qudi/logic/scanning_probe_logic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index ee2260b3b..bb1cd66b6 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -482,7 +482,6 @@ def stop_scan(self): return err - def __scan_poll_loop(self): with self._thread_lock: try: @@ -523,7 +522,10 @@ def __stop_timer(self): else: self.__scan_poll_timer.stop() - def __transform_func(coord, inverse=False): + def __transform_func(self, coord, inverse=False): + # todo: remove workaround, reduce dimension to 3d + coord = {key:val for key, val in list(coord.items())[:3]} + # convert from coordinate dict to plain vector ax_2_idx = lambda ch: ord(ch) - 120 # x->0, y->1, z->2; todo only for these axes transform = self._tilt_corr_transform.__call__ From da091b4def423ba60a3db831f99ab756fcd99b51 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 May 2023 10:24:50 +0200 Subject: [PATCH 25/81] make __transform_func more flexible for arbitrary axes config --- src/qudi/gui/scanning/scannergui.py | 2 ++ src/qudi/logic/scanning_probe_logic.py | 27 ++++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 43f599caf..64a86bd5a 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1076,7 +1076,9 @@ def tilt_corr_support_vector_updated(self): self.toggle_switch_widget.setEnabled(True) self._mw.action_toggle_tilt_correction.setEnabled(True) else: + self.toggle_tilt_correction(False) self._scanning_logic().configure_tilt_correction(None, None) + self.toggle_switch_widget.set_state('Tilt_Correction:OFF') self.toggle_switch_widget.setEnabled(False) self._mw.action_toggle_tilt_correction.setEnabled(False) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index bb1cd66b6..fda2f2d07 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -330,6 +330,8 @@ def toggle_scan(self, start, scan_axes, caller_id=None): def toggle_tilt_correction(self, enable=True, debug_func=False): + target_pos = self._scanner().get_target() + if debug_func: func = self.__func_debug_transform() self.log.info("Set test functions for coord transform") @@ -341,6 +343,9 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): else: self._scanner().set_coordinate_transform(None) + # set target pos again with updated, (dis-) engaged tilt correction + self.set_target_position(target_pos) + def configure_tilt_correction(self, support_vecs=None, shift_vec=None): if support_vecs is None: @@ -373,6 +378,8 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): lin_transform.translate(shift[0], shift[1], shift[2]) self._tilt_corr_transform = lin_transform + # todo, get from compute_reduced_vectors + self._tilt_corr_axes = list(self._scanner().get_constraints().axes)[:3] def __func_debug_transform(self): @@ -523,15 +530,23 @@ def __stop_timer(self): self.__scan_poll_timer.stop() def __transform_func(self, coord, inverse=False): - # todo: remove workaround, reduce dimension to 3d - coord = {key:val for key, val in list(coord.items())[:3]} + """ + Takes a coordinate as dict (with axes keys) and applies the tilt correction transformation. + To this end, reduce dimensionality to 3d on the axes configured for the tilt transformation. + :param coord: dict of the coordinate. Keys are configured scanner axes. + :param inverse: + :return: + """ + # todo: double check: reduce dimension to 3d + coord_reduced = {key:val for key, val in list(coord.items())[:3] if key in self._tilt_corr_axes} + #ax_2_idx_map = {key:idx for (idx, (key, val)) in enumerate(list(coord.items())) if key in self._tilt_corr_axes} # convert from coordinate dict to plain vector - ax_2_idx = lambda ch: ord(ch) - 120 # x->0, y->1, z->2; todo only for these axes transform = self._tilt_corr_transform.__call__ - coord_vec = np.asarray(list(coord.values())).T - coord_transf = transform(coord_vec, invert=inverse).T + coord_vec = np.asarray(list(coord_reduced.values())).T + coord_vec_transf = transform(coord_vec, invert=inverse).T # make dict again after vector rotation - coord_transf = {ax: coord_transf[ax_2_idx(ax)] for (ax, val) in coord.items()} + coord_transf = coord + [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, (ax, val)) in enumerate(coord_reduced.items())] return coord_transf From 0e0c1812bfb8d3ed9783fe9e505c1fd91fdba790 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 May 2023 10:44:40 +0200 Subject: [PATCH 26/81] fix setting new target after tilt correction toggled --- src/qudi/interface/scanning_probe_interface.py | 6 ++++-- src/qudi/logic/scanning_probe_logic.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index b0ac61001..9f9a8ce7f 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -627,10 +627,12 @@ def set_coordinate_transform(self, transform_func): self._coordinate_transform = transform_func def move_absolute(self, position, velocity=None, blocking=False): - return super().move_absolute(self.coordinate_transform(position), velocity, blocking) + new_pos_bare = super().move_absolute(self.coordinate_transform(position), velocity, blocking) + return self.coordinate_transform(new_pos_bare, inverse=True) def move_relative(self, distance, velocity=None, blocking=False): - return super().move_relative(self.coordinate_transform(distance), velocity, blocking) + new_pos_bare = super().move_relative(self.coordinate_transform(distance), velocity, blocking) + return self.coordinate_transform(new_pos_bare, inverse=True) def get_target(self): return self.coordinate_transform(super().get_target(), inverse=True) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index fda2f2d07..ee1e47057 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -342,6 +342,7 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): self._scanner().set_coordinate_transform(func) else: self._scanner().set_coordinate_transform(None) + target_pos = self._scanner().get_target() # set target pos again with updated, (dis-) engaged tilt correction self.set_target_position(target_pos) From a3605a431e527535b0d50c8de282ae084fdbe1c5 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 May 2023 11:29:44 +0200 Subject: [PATCH 27/81] fix checkbox behavior of tilt correction on view/restore default --- src/qudi/gui/scanning/scannergui.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 64a86bd5a..69b4a4f7a 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -402,10 +402,8 @@ def _init_static_dockwidgets(self): sequence=self._optimize_logic().scan_sequence) self.optimizer_dockwidget.setAllowedAreas(QtCore.Qt.TopDockWidgetArea) self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.optimizer_dockwidget) - self.optimizer_dockwidget.visibilityChanged.connect( - self._mw.action_view_optimizer.setChecked) - self._mw.action_view_optimizer.triggered[bool].connect( - self.optimizer_dockwidget.setVisible) + self.optimizer_dockwidget.visibilityChanged.connect(self._mw.action_view_optimizer.setChecked) + self._mw.action_view_optimizer.triggered[bool].connect(self.optimizer_dockwidget.setVisible) # Create a ToggleSwitchWidget self.toggle_switch_widget = ToggleSwitchWidget(switch_states=('Tilt_Correction:OFF', @@ -415,17 +413,16 @@ def _init_static_dockwidgets(self): self.toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) #toggle_switch_widget.setStyleSheet("QToolButton { height: 20px; width: 80px; }") - # Add the widget to the toolbar as a button self._mw.util_toolBar.addWidget(self.toggle_switch_widget) - - self._mw.util_toolBar.visibilityChanged.connect( - self._mw.action_view_toolbar.setChecked) + self._mw.util_toolBar.visibilityChanged.connect(self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) + # Add tilt correction widget to the toolbar as a button self.tilt_correction_dockwidget = TiltCorrectionDockWidget(scanner_axes=self._scanning_logic().scanner_axes) self.tilt_correction_dockwidget.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.tilt_correction_dockwidget) self.tilt_correction_dockwidget.setVisible(False) + self.tilt_correction_dockwidget.visibilityChanged.connect(self._mw.action_view_tilt_correction.setChecked) self._mw.action_view_tilt_correction.triggered[bool].connect(self.tilt_correction_dockwidget.setVisible) @@ -438,6 +435,7 @@ def restore_default_view(self): # Remove all dockwidgets from main window layout self._mw.removeDockWidget(self.optimizer_dockwidget) self._mw.removeDockWidget(self.scanner_control_dockwidget) + self._mw.removeDockWidget(self.tilt_correction_dockwidget) for dockwidget in self.scan_2d_dockwidgets.values(): self._mw.removeDockWidget(dockwidget) for dockwidget in self.scan_1d_dockwidgets.values(): @@ -451,6 +449,12 @@ def restore_default_view(self): self.scanner_control_dockwidget.setFloating(False) self.scanner_control_dockwidget.show() self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.scanner_control_dockwidget) + + # Add tilt correction dock widget + self.tilt_correction_dockwidget.setFloating(False) + self.tilt_correction_dockwidget.setVisible(False) + self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.tilt_correction_dockwidget) + # Add dynamically created dock widgets to layout dockwidgets_2d = tuple(self.scan_2d_dockwidgets.values()) dockwidgets_1d = tuple(self.scan_1d_dockwidgets.values()) @@ -583,6 +587,9 @@ def _add_scan_dockwidget(self, axes): dockwidget.scan_widget.set_marker_bounds(marker_bounds) dockwidget.scan_widget.set_plot_range(x_range=axes_constr[0].value_range) self.scan_1d_dockwidgets[axes] = dockwidget + + # todo not working on view/restore default + #dockwidget.visibilityChanged.connect(self._mw.action_view_line_scan.setChecked) else: if axes in self.scan_2d_dockwidgets: self.log.error('Unable to add scanning widget for axes {0}. Widget for this scan ' From b5b395c402307589530d89c15f824aec3746552f Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 May 2023 17:07:05 +0200 Subject: [PATCH 28/81] fix set_target_position with new pos {new_pos}. Bare {self._scanner()._get_position_bare()}") if any(pos != new_pos[ax] for ax, pos in pos_dict.items()): caller_id = None #self.log.debug(f"Logic set target with id {caller_id} to new: {new_pos}") From 7bab5a133c743a9948cb675c097a2631afd36018 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 May 2023 17:21:57 +0200 Subject: [PATCH 29/81] fix copy bug in __transform_func --- src/qudi/logic/scanning_probe_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index a0a42b357..95b43e6a7 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -553,7 +553,7 @@ def __transform_func(self, coord, inverse=False): coord_vec = np.asarray(list(coord_reduced.values())).T coord_vec_transf = transform(coord_vec, invert=inverse).T # make dict again after vector rotation - coord_transf = coord + coord_transf = cp.copy(coord) [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, (ax, val)) in enumerate(coord_reduced.items())] return coord_transf From fe0e5dded247e2c9e2eb418babcc13f109c762ea Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 23 May 2023 12:48:40 +0200 Subject: [PATCH 30/81] auto reduce dimensions on correct axes --- src/qudi/logic/scanning_probe_logic.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 95b43e6a7..7fa859bc2 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -31,7 +31,7 @@ from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar from qudi.util.basis_transformations.basis_transformation \ - import compute_rotation_mat_rodriguez, compute_reduced_vectors + import compute_rotation_mat_rodriguez, compute_reduced_vectors, det_changing_axes class ScanningProbeLogic(LogicBase): """ @@ -314,6 +314,7 @@ def set_target_position(self, pos_dict, caller_id=None, move_blocking=False): new_pos[ax] = ax_constr[ax].clip_value(pos) if pos != new_pos[ax]: + # todo: check bounds on transformed (bare) hw coordinates self.log.warning('Scanner position target value out of bounds for axis "{0}". ' 'Clipping value to {1:.3e}.'.format(ax, new_pos[ax])) @@ -365,17 +366,20 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") if shift_vec is None: - # todo: for the case of 3d vectors in plane, throws out too many dimensions - red_support_vecs = support_vecs #compute_reduced_vectors(support_vecs) + red_support_vecs = compute_reduced_vectors(support_vecs) shift_vec = np.mean(red_support_vecs, axis=0) else: shift_vec = np.asarray(shift_vec) red_support_vecs = np.vstack([support_vecs, shift_vec])# compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) + red_support_vecs = compute_reduced_vectors(red_support_vecs)[:-1,:] shift_vec = red_support_vecs[-1,:] - # todo remove workaround code for dim reduction - red_support_vecs = red_support_vecs[:,:3] - shift_vec = shift_vec[:3] + tilt_axes = det_changing_axes(support_vecs) + + if red_support_vecs.shape != (3,3) or shift_vec.shape[0] != 3: + n_dim = support_vecs.shape[1] + raise ValueError(f"Can't calculate tilt in >3 dimensions. " + f"Given support vectors (dim= {n_dim}) must be constant in {n_dim-3} dims. ") rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec @@ -385,8 +389,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): lin_transform.translate(shift[0], shift[1], shift[2]) self._tilt_corr_transform = lin_transform - # todo, get from compute_reduced_vectors - self._tilt_corr_axes = list(self._scanner().get_constraints().axes)[:3] + self._tilt_corr_axes = [el for idx, el in enumerate(self._scanner().get_constraints().axes) if tilt_axes[idx]] def __func_debug_transform(self): From 6468f2635c12b4f170353e7f0d39cf1abfd33b2d Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 23 May 2023 13:25:56 +0200 Subject: [PATCH 31/81] disable tilt correction button on invalid support vectors and failed configure_tilt() --- src/qudi/gui/scanning/scannergui.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 69b4a4f7a..7a9341ccf 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1074,6 +1074,13 @@ def tilt_corr_support_vector_updated(self): vecs_valid = [support_vecs[vec][dim[0]].is_valid for dim in dim_idxs] all_vecs_valid = np.all(vecs_valid) and all_vecs_valid + self.toggle_tilt_correction(False) + self._scanning_logic().configure_tilt_correction(None, None) + + self.toggle_switch_widget.set_state('Tilt_Correction:OFF') + self.toggle_switch_widget.setEnabled(False) + self._mw.action_toggle_tilt_correction.setEnabled(False) + if all_vecs_valid: shift_vec = support_vecs_val[-1] if not np.all([np.isfinite(el) for el in shift_vec]): @@ -1082,13 +1089,9 @@ def tilt_corr_support_vector_updated(self): shift_vec) self.toggle_switch_widget.setEnabled(True) self._mw.action_toggle_tilt_correction.setEnabled(True) - else: - self.toggle_tilt_correction(False) - self._scanning_logic().configure_tilt_correction(None, None) - self.toggle_switch_widget.set_state('Tilt_Correction:OFF') - self.toggle_switch_widget.setEnabled(False) - self._mw.action_toggle_tilt_correction.setEnabled(False) + + def toggle_tilt_correction(self, state): # toggle switch From 4871c60cb5c047a9bf8bdedf176ddd245356c98a Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 23 May 2023 13:36:21 +0200 Subject: [PATCH 32/81] connect signals to support vector spin boxes --- src/qudi/gui/scanning/scannergui.py | 5 ++--- src/qudi/logic/scanning_probe_logic.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 7a9341ccf..a8b44dfd4 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -255,7 +255,8 @@ def on_activate(self): QtCore.Qt.QueuedConnection) self._mw.action_toggle_tilt_correction.triggered.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) - + [box.valueChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) + for box_row in tilt_widget.support_vecs_box for box in box_row] self.tilt_corr_support_vector_updated() # Initialize dockwidgets to default view @@ -1091,8 +1092,6 @@ def tilt_corr_support_vector_updated(self): self._mw.action_toggle_tilt_correction.setEnabled(True) - - def toggle_tilt_correction(self, state): # toggle switch if type(state) == str: diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 7fa859bc2..ecb1fca10 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -379,7 +379,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): if red_support_vecs.shape != (3,3) or shift_vec.shape[0] != 3: n_dim = support_vecs.shape[1] raise ValueError(f"Can't calculate tilt in >3 dimensions. " - f"Given support vectors (dim= {n_dim}) must be constant in {n_dim-3} dims. ") + f"Given support vectors (dim= {n_dim}) must be constant in exactly {n_dim-3} dims. ") rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec From 9e22c3e3f87cb2cabddc8bf794bc927b40203c81 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 26 May 2023 11:44:58 +0200 Subject: [PATCH 33/81] Fix infinite movement loop with enabled tilt correction. Add support for tilted scans. --- .../interfuse/ni_scanning_probe_interfuse.py | 119 ++++++++++-------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index fc7de2448..63a00b60c 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -276,8 +276,8 @@ def configure_scan(self, scan_settings): self._ni_finite_sampling_io().set_sample_rate(frequency) self._ni_finite_sampling_io().set_active_channels( input_channels=(self._ni_channel_mapping[in_ch] for in_ch in self._input_channel_units), - output_channels=(self._ni_channel_mapping[ax] for ax in axes) - # TODO Use all axes and keep the unused constant? basically just constants in ni scan dict. + output_channels=(self._ni_channel_mapping[ax] for ax in self.get_constraints().axes.keys()) + #output_channels = (self._ni_channel_mapping[ax] for ax in axes) ) self._ni_finite_sampling_io().set_output_mode(SamplingOutputMode.JUMP_LIST) @@ -371,12 +371,7 @@ def get_position(self): @return dict: current position per axis. """ - with self._thread_lock_cursor: - if not self._ao_setpoint_channels_active: - self._toggle_ao_setpoint_channels(True) - - pos = self._voltage_dict_to_position_dict(self._ni_ao().setpoints) - return pos + return self._get_position() def start_scan(self): try: @@ -630,29 +625,15 @@ def _voltage_dict_to_position_dict(self, voltages): return positions_data - def _initialize_ni_scan_arrays(self, scan_data): - """ - @param ScanData scan_data: The desired ScanData instance - - @return dict: Where keys coincide with the ni_channel for the current scan axes and values are the - corresponding voltage 1D numpy arrays for each axis - """ - - # TODO adjust toolchain to incorporate __backwards_line_resolution in settings? - # TODO maybe need to clip to voltage range in case of float precision error in conversion? - - assert isinstance(scan_data, ScanData), 'This function requires a scan_data object as input' - + def _get_scan_lines(self, scan_data): if scan_data.scan_dimension == 1: axis = scan_data.scan_axes[0] horizontal_resolution = scan_data.scan_resolution[0] - horizontal = np.linspace(*self._position_to_voltage(axis, scan_data.scan_range[0]), + horizontal = np.linspace(scan_data.scan_range[0][0], scan_data.scan_range[0][1], horizontal_resolution) - - horizontal_return_line = np.linspace(self._position_to_voltage(axis, scan_data.scan_range[0][1]), - self._position_to_voltage(axis, scan_data.scan_range[0][0]), + horizontal_return_line = np.linspace(scan_data.scan_range[0][1], scan_data.scan_range[0][0], self.__backwards_line_resolution) # TODO Return line for 1d included due to possible hysteresis. Might be able to drop it, # but then get_scan_data needs to be changed accordingly @@ -660,9 +641,7 @@ def _initialize_ni_scan_arrays(self, scan_data): horizontal_single_line = np.concatenate((horizontal, horizontal_return_line)) - voltage_dict = {self._ni_channel_mapping[axis]: horizontal_single_line} - - return voltage_dict + coord_dict = {axis: horizontal_single_line} elif scan_data.scan_dimension == 2: @@ -672,11 +651,11 @@ def _initialize_ni_scan_arrays(self, scan_data): # horizontal scan array / "fast axis" horizontal_axis = scan_data.scan_axes[0] - horizontal = np.linspace(*self._position_to_voltage(horizontal_axis, scan_data.scan_range[0]), + horizontal = np.linspace(scan_data.scan_range[0][0], scan_data.scan_range[0][1], horizontal_resolution) - horizontal_return_line = np.linspace(self._position_to_voltage(horizontal_axis, scan_data.scan_range[0][1]), - self._position_to_voltage(horizontal_axis, scan_data.scan_range[0][0]), + horizontal_return_line = np.linspace(scan_data.scan_range[0][1], + scan_data.scan_range[0][0], self.__backwards_line_resolution) # a single back and forth line horizontal_single_line = np.concatenate((horizontal, horizontal_return_line)) @@ -684,10 +663,8 @@ def _initialize_ni_scan_arrays(self, scan_data): horizontal_scan_array = np.tile(horizontal_single_line, vertical_resolution) # vertical scan array / "slow axis" - vertical_axis = scan_data.scan_axes[1] - - vertical = np.linspace(*self._position_to_voltage(vertical_axis, scan_data.scan_range[1]), + vertical = np.linspace(scan_data.scan_range[1][0], scan_data.scan_range[1][1], vertical_resolution) # during horizontal line, the vertical line keeps its value @@ -703,20 +680,51 @@ def _initialize_ni_scan_arrays(self, scan_data): # TODO We could drop the last return line in the initialization, as it is not read in anyways till yet. - voltage_dict = { - self._ni_channel_mapping[horizontal_axis]: horizontal_scan_array, - self._ni_channel_mapping[vertical_axis]: vertical_scan_array + coord_dict = {horizontal_axis: horizontal_scan_array, + vertical_axis: vertical_scan_array } - return voltage_dict else: - raise NotImplementedError('Ni Scan arrays could not be initialized for given ScanData dimension') + raise ValueError(f"Not supported scan dimension: {scan_data.scan_dimension}") + + # expand coord dict to full dimension, setting unsued axes to constant + scanner_axes = self.get_constraints().axes + axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord_dict.keys()] + coord_unused = {} + + for ax in axes_unused: + target_coord = self.get_target()[ax] + coords = np.ones(list(coord_dict.values())[0].shape)* target_coord + coord_unused[ax] = coords + + self.log.debug(f"Expanding scan coord {coord_dict} with unused {coord_unused}") + coord_dict.update(coord_unused) + + return coord_dict + + def _initialize_ni_scan_arrays(self, scan_data): + """ + @param ScanData scan_data: The desired ScanData instance + + @return dict: Where keys coincide with the ni_channel for the current scan axes and values are the + corresponding voltage 1D numpy arrays for each axis + """ + + # TODO adjust toolchain to incorporate __backwards_line_resolution in settings? + # TODO maybe need to clip to voltage range in case of float precision error in conversion? + + assert isinstance(scan_data, ScanData), 'This function requires a scan_data object as input' + + scan_coords = self._get_scan_lines(scan_data) + scan_voltages = {self._ni_channel_mapping[ax]: self._position_to_voltage(ax, val) for ax, val in scan_coords.items()} + + return scan_voltages def __ao_cursor_write_loop(self): t_start = time.perf_counter() try: - current_pos_vec = self._pos_dict_to_vec(self.get_position()) + current_pos_vec = self._pos_dict_to_vec(self._get_position()) with self._thread_lock_cursor: stop_loop = self._abort_cursor_move @@ -754,7 +762,7 @@ def __ao_cursor_write_loop(self): self._ni_ao().setpoints = new_voltage #self.log.debug(f'Cursor_write_loop move to {new_pos}, Dist= {distance_to_target} ' - # f'took {1e3*(time.perf_counter()-t_start)} ms.') + # f' to target {self._target_pos} took {1e3*(time.perf_counter()-t_start)} ms.') # Start single-shot timer to call this follow loop again after some wait time t_overhead = time.perf_counter() - t_start @@ -846,6 +854,18 @@ def _prepare_movement(self, position, velocity=None): #self.log.debug("Movement prepared") # TODO Keep other axis constant? + def _get_position(self): + """ + Raw position of scanner hardware. Interface method .get_position() might + get wrapped by tilt correction and transform to a virtual coord system. + """ + with self._thread_lock_cursor: + if not self._ao_setpoint_channels_active: + self._toggle_ao_setpoint_channels(True) + + pos = self._voltage_dict_to_position_dict(self._ni_ao().setpoints) + return pos + def __init_ao_timer(self): self.__ni_ao_write_timer = QtCore.QTimer(parent=self) @@ -926,16 +946,11 @@ def is_full(self): class NiScanningProbeInterfuseCorrected(CoordinateTransformMixin, NiScanningProbeInterfuse): - def set_coordinate_transform(self, transform_func): - # todo: if set with a transform_func - # pass, the _init_ni_scan_array must change - pass - - def _initialize_ni_scan_arrays(self, scan_data): - # todo: complete pseudo code below - #vectors = super()._initialize_ni_scan_arrays() - #vectors_transormed = self.coordinate_transform(vectors) - #return vectors_transormed + scan_coords = self._get_scan_lines(scan_data) + scan_coords_transf = self.coordinate_transform(scan_coords, inverse=False) + scan_voltages_transformed = {self._ni_channel_mapping[ax]: self._position_to_voltage(ax, val) + for ax, val in scan_coords_transf.items()} + + return scan_voltages_transformed - pass \ No newline at end of file From 898cddf9d07c6b5fb15b4521db5d83341d84e6ee Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 26 May 2023 11:45:57 +0200 Subject: [PATCH 34/81] add coordinate_transform_enabled property --- src/qudi/interface/scanning_probe_interface.py | 4 ++++ src/qudi/logic/scanning_probe_logic.py | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 9f9a8ce7f..ebe3208d4 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -44,6 +44,10 @@ def set_coordinate_transform(self, transform_func): if transform_func is not None: raise ValueError('Coordinate transformation not supported by scanning hardware.') + @property + def coordinate_transform_enabled(self): + return self._coordinate_transform is not None + @property def supports_coordinate_transform(self): # todo: this should be part of the scanconstraints diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 7fa859bc2..6a9ba575a 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -338,6 +338,7 @@ def toggle_scan(self, start, scan_axes, caller_id=None): def toggle_tilt_correction(self, enable=True, debug_func=False): target_pos = self._scanner().get_target() + is_enabled = self._scanner().coordinate_transform_enabled if debug_func: func = self.__func_debug_transform() @@ -351,8 +352,9 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): self._scanner().set_coordinate_transform(None) target_pos = self._scanner().get_target() - # set target pos again with updated, (dis-) engaged tilt correction - self.set_target_position(target_pos) + if enable != is_enabled: + # set target pos again with updated, (dis-) engaged tilt correction + self.set_target_position(target_pos, move_blocking=True) def configure_tilt_correction(self, support_vecs=None, shift_vec=None): @@ -557,6 +559,8 @@ def __transform_func(self, coord, inverse=False): coord_vec_transf = transform(coord_vec, invert=inverse).T # make dict again after vector rotation coord_transf = cp.copy(coord) - [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, (ax, val)) in enumerate(coord_reduced.items())] + [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, (ax, val)) in enumerate(coord_reduced.items())] + + #self.log.debug(f"Tranforming {coord} => {coord_transf}") return coord_transf From 4487aab4e70721213f7d186c8cdc8b9a51504509 Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 28 May 2023 09:38:19 +0200 Subject: [PATCH 35/81] add function to expand coordinate dict to all axes --- .../interfuse/ni_scanning_probe_interfuse.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 63a00b60c..5057a7954 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -687,20 +687,7 @@ def _get_scan_lines(self, scan_data): else: raise ValueError(f"Not supported scan dimension: {scan_data.scan_dimension}") - # expand coord dict to full dimension, setting unsued axes to constant - scanner_axes = self.get_constraints().axes - axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord_dict.keys()] - coord_unused = {} - - for ax in axes_unused: - target_coord = self.get_target()[ax] - coords = np.ones(list(coord_dict.values())[0].shape)* target_coord - coord_unused[ax] = coords - - self.log.debug(f"Expanding scan coord {coord_dict} with unused {coord_unused}") - coord_dict.update(coord_unused) - - return coord_dict + return self._expand_coordinate(coord_dict) def _initialize_ni_scan_arrays(self, scan_data): """ @@ -891,7 +878,29 @@ def __start_ao_write_timer(self): except: self.log.exception("") + def _expand_coordinate(self, coord): + """ + Expand coord dict to all scanner dimensions, setting missing axes to current scanner target. + """ + + scanner_axes = self.get_constraints().axes + current_target = self.get_target() + len_coord = 0 + axes_unused = scanner_axes.keys() + + if coord: + len_coord = len(list(coord.values())[0]) + axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord.keys()] + coord_unused = {} + + for ax in axes_unused: + target_coord = current_target[ax] + coords = np.ones(len_coord)*target_coord if len_coord > 1 else target_coord + coord_unused[ax] = coords + + coord.update(coord_unused) + return coord class RawDataContainer: def __init__(self, channel_keys, number_of_scan_lines, forward_line_resolution, backwards_line_resolution): From 245723f39b0e091d57fd7d3bd1d6bb907345702a Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 28 May 2023 09:38:19 +0200 Subject: [PATCH 36/81] check value range in transformed coordinates when setting target in probe logic --- .../interfuse/ni_scanning_probe_interfuse.py | 15 +--------- .../interface/scanning_probe_interface.py | 24 +++++++++++++++ src/qudi/logic/scanning_probe_logic.py | 29 +++++++++---------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 63a00b60c..a990c3460 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -687,20 +687,7 @@ def _get_scan_lines(self, scan_data): else: raise ValueError(f"Not supported scan dimension: {scan_data.scan_dimension}") - # expand coord dict to full dimension, setting unsued axes to constant - scanner_axes = self.get_constraints().axes - axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord_dict.keys()] - coord_unused = {} - - for ax in axes_unused: - target_coord = self.get_target()[ax] - coords = np.ones(list(coord_dict.values())[0].shape)* target_coord - coord_unused[ax] = coords - - self.log.debug(f"Expanding scan coord {coord_dict} with unused {coord_unused}") - coord_dict.update(coord_unused) - - return coord_dict + return self._expand_coordinate(coord_dict) def _initialize_ni_scan_arrays(self, scan_data): """ diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index ebe3208d4..c5b78d801 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -164,6 +164,30 @@ def emergency_stop(self): """ pass + def _expand_coordinate(self, coord): + """ + Expand coord dict to all scanner dimensions, setting missing axes to current scanner target. + """ + + scanner_axes = self.get_constraints().axes + current_target = self.get_target() + len_coord = 0 + axes_unused = scanner_axes.keys() + + if coord: + len_coord = np.asarray((list(coord.values())[0])).size + axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord.keys()] + coord_unused = {} + + for ax in axes_unused: + target_coord = current_target[ax] + coords = np.ones(len_coord)*target_coord if len_coord > 1 else target_coord + coord_unused[ax] = coords + + coord.update(coord_unused) + + return coord + class ScanData: """ diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 9de9a684b..ec161db18 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -298,12 +298,11 @@ def set_target_position(self, pos_dict, caller_id=None, move_blocking=False): self.sigScannerTargetChanged.emit(new_pos, self.module_uuid) return new_pos - #self.log.debug(f"Set pos to= {pos_dict}") + #self.log.debug(f"Requested Set pos to= {pos_dict}") ax_constr = self.scanner_constraints.axes - old_target = self.scanner_target - new_pos = pos_dict.copy() - old_target.update(new_pos) # tilt correction needs all coordinates, not only the new ones given - new_pos = old_target + pos_dict = self._scanner()._expand_coordinate(pos_dict) + + pos_dict = self._scanner().coordinate_transform(pos_dict) for ax, pos in pos_dict.items(): if ax not in ax_constr: @@ -312,21 +311,21 @@ def set_target_position(self, pos_dict, caller_id=None, move_blocking=False): self.sigScannerTargetChanged.emit(new_pos, self.module_uuid) return new_pos - new_pos[ax] = ax_constr[ax].clip_value(pos) - if pos != new_pos[ax]: - # todo: check bounds on transformed (bare) hw coordinates - self.log.warning('Scanner position target value out of bounds for axis "{0}". ' - 'Clipping value to {1:.3e}.'.format(ax, new_pos[ax])) + pos_dict[ax] = ax_constr[ax].clip_value(pos) + if pos != pos_dict[ax]: + self.log.warning(f'Scanner position target value {pos:.3e} out of bounds for axis "{ax}". ' + f'Clipping value to {pos_dict[ax]:.3e}.') + + # move_absolute expects untransformed coordinatess, so invert clipped pos + pos_dict = self._scanner().coordinate_transform(pos_dict, inverse=True) - new_pos = self._scanner().move_absolute(new_pos, blocking=move_blocking) + new_pos = self._scanner().move_absolute(pos_dict, blocking=move_blocking) #self.log.debug(f"Set pos to= {pos_dict} => new pos {new_pos}. Bare {self._scanner()._get_position_bare()}") if any(pos != new_pos[ax] for ax, pos in pos_dict.items()): caller_id = None #self.log.debug(f"Logic set target with id {caller_id} to new: {new_pos}") - self.sigScannerTargetChanged.emit( - new_pos, - self.module_uuid if caller_id is None else caller_id - ) + self.sigScannerTargetChanged.emit(new_pos, + self.module_uuid if caller_id is None else caller_id) return new_pos def toggle_scan(self, start, scan_axes, caller_id=None): From aee92990fc66921141571ad655b1409a494c92b5 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 30 May 2023 17:35:30 +0200 Subject: [PATCH 37/81] fix some bugs with tilt correction enabled --- .../interfuse/ni_scanning_probe_interfuse.py | 53 +++++++------------ src/qudi/logic/scanning_probe_logic.py | 15 ++++-- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 5057a7954..3f06f9105 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -163,7 +163,7 @@ def on_activate(self): square_px_only=False, allow_coordinate_transform=self.supports_coordinate_transform) # TODO incorporate in scanning_probe toolchain # - self._target_pos = self.get_position() # get voltages/pos from ni_ao + self._target_pos = self._get_position() # get voltages/pos from ni_ao self._toggle_ao_setpoint_channels(False) # And free ao resources after that self._t_last_move = time.perf_counter() self.__init_ao_timer() @@ -307,11 +307,11 @@ def move_absolute(self, position, velocity=None, blocking=False): # assert not self.is_running, 'Cannot move the scanner while, scan is running' if self.is_scan_running: self.log.error('Cannot move the scanner while, scan is running') - return self.get_target() + return self._get_target() if not set(position).issubset(self.get_constraints().axes): self.log.error('Invalid axes name in position') - return self.get_target() + return self._get_target() try: self._prepare_movement(position, velocity=velocity) @@ -322,7 +322,7 @@ def move_absolute(self, position, velocity=None, blocking=False): self._t_last_move = time.perf_counter() - return self.get_target() + return self._get_target() except: self.log.exception("Couldn't move: ") @@ -345,7 +345,7 @@ def move_relative(self, distance, velocity=None, blocking=False): Log error and return current target position if something fails or a 1D/2D scan is in progress. """ - current_position = self.get_position() + current_position = self._get_position() end_pos = {ax: current_position[ax] + distance[ax] for ax in distance} self.move_absolute(end_pos, velocity=velocity, blocking=blocking) @@ -357,10 +357,7 @@ def get_target(self): @return dict: current target position per axis. """ - if self.is_scan_running: - return self._stored_target_pos - else: - return self._target_pos + return self._get_target() def get_position(self): """ Get a snapshot of the actual scanner position (i.e. from position feedback sensors). @@ -407,7 +404,7 @@ def _start_scan(self): with self._thread_lock_data: self._scan_data.new_scan() #self.log.debug(f"New scan data: {self._scan_data.data}, position {self._scan_data._position_data}") - self._stored_target_pos = self.get_target().copy() + self._stored_target_pos = self._get_target().copy() self._scan_data.scanner_target_at_start = self._stored_target_pos # todo: scanning_probe_logic exits when scanner not locked right away @@ -782,7 +779,7 @@ def _abort_cursor_movement(self): """ #self.log.debug(f"Aborting move.") - self._target_pos = self.get_position() + self._target_pos = self._get_position() with self._thread_lock_cursor: @@ -826,7 +823,7 @@ def _prepare_movement(self, position, velocity=None): # TODO Adapt interface to use "in_range"? self._target_pos[axis] = position[axis] - #self.log.debug(f"New target pos: {self._target_pos}") + self.log.debug(f"New target pos: {self._target_pos}") # TODO Add max velocity as a hardware constraint/ Calculate from scan_freq etc? if velocity is None: @@ -853,6 +850,16 @@ def _get_position(self): pos = self._voltage_dict_to_position_dict(self._ni_ao().setpoints) return pos + def _get_target(self): + """ Raw target position of the scanner hardware. Interface method .get_target() might + get wrapped by tilt correction and transform to a virtual coord system. + + """ + if self.is_scan_running: + return self._stored_target_pos + else: + return self._target_pos + def __init_ao_timer(self): self.__ni_ao_write_timer = QtCore.QTimer(parent=self) @@ -878,29 +885,7 @@ def __start_ao_write_timer(self): except: self.log.exception("") - def _expand_coordinate(self, coord): - """ - Expand coord dict to all scanner dimensions, setting missing axes to current scanner target. - """ - - scanner_axes = self.get_constraints().axes - current_target = self.get_target() - len_coord = 0 - axes_unused = scanner_axes.keys() - - if coord: - len_coord = len(list(coord.values())[0]) - axes_unused = [ax for ax in scanner_axes.keys() if ax not in coord.keys()] - coord_unused = {} - - for ax in axes_unused: - target_coord = current_target[ax] - coords = np.ones(len_coord)*target_coord if len_coord > 1 else target_coord - coord_unused[ax] = coords - - coord.update(coord_unused) - return coord class RawDataContainer: def __init__(self, channel_keys, number_of_scan_lines, forward_line_resolution, backwards_line_resolution): diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index ec161db18..5d268aafd 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -23,6 +23,7 @@ from PySide2 import QtCore import copy as cp import numpy as np +from collections import OrderedDict from qudi.core.module import LogicBase from qudi.util.mutex import RecursiveMutex @@ -300,7 +301,8 @@ def set_target_position(self, pos_dict, caller_id=None, move_blocking=False): #self.log.debug(f"Requested Set pos to= {pos_dict}") ax_constr = self.scanner_constraints.axes - pos_dict = self._scanner()._expand_coordinate(pos_dict) + pos_dict = self._scanner()._expand_coordinate(cp.copy(pos_dict)) + #self.log.debug(f"Expand to= {pos_dict}") pos_dict = self._scanner().coordinate_transform(pos_dict) @@ -316,9 +318,10 @@ def set_target_position(self, pos_dict, caller_id=None, move_blocking=False): self.log.warning(f'Scanner position target value {pos:.3e} out of bounds for axis "{ax}". ' f'Clipping value to {pos_dict[ax]:.3e}.') + # move_absolute expects untransformed coordinatess, so invert clipped pos pos_dict = self._scanner().coordinate_transform(pos_dict, inverse=True) - + #self.log.debug(f"In front of hw.move_abs {pos_dict}") new_pos = self._scanner().move_absolute(pos_dict, blocking=move_blocking) #self.log.debug(f"Set pos to= {pos_dict} => new pos {new_pos}. Bare {self._scanner()._get_position_bare()}") if any(pos != new_pos[ax] for ax, pos in pos_dict.items()): @@ -371,9 +374,10 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): shift_vec = np.mean(red_support_vecs, axis=0) else: shift_vec = np.asarray(shift_vec) - red_support_vecs = np.vstack([support_vecs, shift_vec])# compute_reduced_vectors(np.vstack([support_vecs, shift_vec])) - red_support_vecs = compute_reduced_vectors(red_support_vecs)[:-1,:] - shift_vec = red_support_vecs[-1,:] + red_support_vecs = np.vstack([support_vecs, shift_vec]) + red_vecs = compute_reduced_vectors(red_support_vecs) + red_support_vecs = red_vecs[:-1,:] + shift_vec = red_vecs[-1,:] tilt_axes = det_changing_axes(support_vecs) @@ -550,6 +554,7 @@ def __transform_func(self, coord, inverse=False): """ # todo: double check: reduce dimension to 3d coord_reduced = {key:val for key, val in list(coord.items())[:3] if key in self._tilt_corr_axes} + coord_reduced = OrderedDict(sorted(coord_reduced.items())) #ax_2_idx_map = {key:idx for (idx, (key, val)) in enumerate(list(coord.items())) if key in self._tilt_corr_axes} # convert from coordinate dict to plain vector From cf120df07507f868120821993c0e2c5d202a771f Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 5 Jun 2023 15:01:19 +0200 Subject: [PATCH 38/81] fix unwanted jumped on toggling tilt correction off --- src/qudi/logic/scanning_probe_logic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 5d268aafd..bf2deae54 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -352,7 +352,6 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): self._scanner().set_coordinate_transform(func) else: self._scanner().set_coordinate_transform(None) - target_pos = self._scanner().get_target() if enable != is_enabled: # set target pos again with updated, (dis-) engaged tilt correction From 860c778886d9fbd29832d66c64d468db9ccb6e94 Mon Sep 17 00:00:00 2001 From: timoML Date: Sat, 17 Jun 2023 10:40:24 +0200 Subject: [PATCH 39/81] save tilt correction support vectors as StatusVar --- src/qudi/gui/scanning/scannergui.py | 38 +++++++++------ .../scanning/tilt_correction_dcokwidget.py | 46 ++++++++++++++++++- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index a8b44dfd4..7362d661f 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -113,6 +113,7 @@ class ScannerGui(GuiBase): # status vars _window_state = StatusVar(name='window_state', default=None) _window_geometry = StatusVar(name='window_geometry', default=None) + _tilt_correction_vectors = StatusVar(name='tilt_correction_vectors', default=[]) # signals sigScannerTargetChanged = QtCore.Signal(dict, object) @@ -257,7 +258,6 @@ def on_activate(self): QtCore.Qt.QueuedConnection) [box.valueChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) for box_row in tilt_widget.support_vecs_box for box in box_row] - self.tilt_corr_support_vector_updated() # Initialize dockwidgets to default view self.restore_default_view() @@ -266,12 +266,15 @@ def on_activate(self): self.restore_history() self._restore_window_geometry(self._mw) + self._restore_tilt_correction() self._send_pop_up_message('We would appreciate your contribution', 'The scanning probe toolchain is still in active development. ' 'Please report bugs and issues in the qudi-iqo-modules repository ' 'or even fix them and contribute your pull request. Your help is highly appreciated.') + + return def on_deactivate(self): @@ -527,6 +530,15 @@ def restore_default_view(self): return + def _restore_tilt_correction(self): + if self._tilt_correction_vectors: + for idx, vector in enumerate(self._tilt_correction_vectors): + try: + self.tilt_correction_dockwidget.set_support_vector(vector, idx) + except (ValueError, KeyError): + pass + self.tilt_corr_support_vector_updated() + @QtCore.Slot(tuple) def save_scan_data(self, scan_axes=None): """ @@ -1057,17 +1069,15 @@ def update_optimizer_settings(self, settings=None): def tilt_corr_set_support_vector(self, idx_vector=0): target = self._scanning_logic().scanner_target - dim_idxs = [(idx, key) for idx,key in enumerate(target.keys())] - support_vecs = self.tilt_correction_dockwidget.support_vecs_box - - [support_vecs[idx_vector][dim[0]].setValue(target[dim[1]]) for dim in dim_idxs] - + self.tilt_correction_dockwidget.set_support_vector(target, idx_vector) self.tilt_corr_support_vector_updated() def tilt_corr_support_vector_updated(self): support_vecs = self.tilt_correction_dockwidget.support_vecs_box support_vecs_val = self.tilt_correction_dockwidget.support_vectors + self._tilt_correction_vectors = support_vecs_val + dim_idxs = [(idx, key) for idx, key in enumerate(self._scanning_logic().scanner_axes.keys())] all_vecs_valid = True @@ -1083,15 +1093,15 @@ def tilt_corr_support_vector_updated(self): self._mw.action_toggle_tilt_correction.setEnabled(False) if all_vecs_valid: - shift_vec = support_vecs_val[-1] - if not np.all([np.isfinite(el) for el in shift_vec]): - shift_vec = None - self._scanning_logic().configure_tilt_correction(support_vecs_val[:-1], - shift_vec) + shift_vec_arr = self.tilt_correction_dockwidget.vector_dict_2_array(support_vecs_val[-1]) + if not np.all([np.isfinite(el) for el in shift_vec_arr]): + shift_vec_arr = None + support_vecs_arr = self.tilt_correction_dockwidget.vector_dict_2_array(support_vecs_val[:-1]) + self._scanning_logic().configure_tilt_correction(support_vecs_arr, + shift_vec_arr) self.toggle_switch_widget.setEnabled(True) self._mw.action_toggle_tilt_correction.setEnabled(True) - def toggle_tilt_correction(self, state): # toggle switch if type(state) == str: @@ -1102,8 +1112,8 @@ def toggle_tilt_correction(self, state): self._scanning_logic().toggle_tilt_correction(enabled) - icon_on = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt.svg")) - icon_off = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_off.svg")) + icon_on = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_toggle.svg")) + icon_off = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_toggle_off.svg")) if enabled: self._mw.action_toggle_tilt_correction.setIcon(icon_on) else: diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 9711f6978..9a5fef8d7 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -22,6 +22,7 @@ __all__ = ('TiltCorrectionDockWidget') import numpy as np +from collections import OrderedDict from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtWidgets import QDockWidget, QWidget,QGridLayout, QLabel, QPushButton,QTableWidget @@ -31,10 +32,12 @@ #from qudi.gui.scanning.scan_dockwidget import ScanDockWidget class TiltCorrectionDockWidget(QDockWidget): + def __init__(self, parent=None, scanner_axes=None): super(TiltCorrectionDockWidget, self).__init__(parent) self._n_dim = len(scanner_axes) + self._scan_axes = OrderedDict(scanner_axes) self.setWindowTitle("Tilt Correction") # Create the dock widget contents @@ -45,7 +48,7 @@ def __init__(self, parent=None, scanner_axes=None): tiltpoint_label = QLabel("Support Vectors") dock_widget_layout.addWidget(tiltpoint_label, 0, 0) - for idx, ax in enumerate(list(scanner_axes.keys())): + for idx, ax in enumerate(list(self._scan_axes.keys())): tiltpoint_label = QLabel(ax) dock_widget_layout.addWidget(tiltpoint_label, 0, 1+idx) @@ -99,10 +102,49 @@ def support_vectors(self): vec_vals = [] for vec in support_vecs: - vec_vals.append([box.value() for box in vec]) + vals = [box.value() if box.is_valid else np.nan for box in vec] + vec_vals.append({list(self._scan_axes.keys())[idx]: vals[idx] for idx, box in enumerate(vec)}) return vec_vals + def set_support_vector(self, vector, idx): + """ + vector: dict with key= axis and value= vector component. + idx: index of support vector in [0,1,2,3] + """ + dim_idxs = [(idx, key) for idx, key in enumerate(self._scan_axes.keys())] + + if idx in [0,1,2] and vector in self.support_vectors[:3]: + [self.support_vecs_box[idx][idx_ax].setValue(np.nan) for idx_ax, ax in dim_idxs] + raise ValueError(f"Can't set support vector {idx} to {vector}. Vectors need to be distinct, " + f"but found: {self.support_vectors[:3]}") + + [self.support_vecs_box[idx][idx_ax].setValue(vector[ax]) for idx_ax, ax in dim_idxs] + + def vector_dict_2_array(self, vector): + """ + Convert vectors given as dict (with axes keys) to arrays and ensure correct order. + vector: dict or list of dicts + + return: np.array or list of np.array + """ + + if type(vector) != list: + vector = [vector] + + vecs_arr = [] + for vec in vector: + vec_arr = np.ones((len(self._scan_axes)))*np.nan + for idx, ax in enumerate(self._scan_axes): + vec_arr[idx] = vec[ax] + + assert np.all(~np.isnan(vec_arr)) + vecs_arr.append(vec_arr) + + if len(vec_arr) == 1: + return vecs_arr[0] + return vecs_arr + @property def auto_origin(self): return True if self.auto_origin_switch.switch_state == 'ON' else False From bb494ba41d9ae8e53f65b463b9d01d0b9ac418fa Mon Sep 17 00:00:00 2001 From: timoML Date: Sat, 17 Jun 2023 11:11:06 +0200 Subject: [PATCH 40/81] small fix --- src/qudi/gui/scanning/tilt_correction_dcokwidget.py | 2 +- src/qudi/logic/scanning_probe_logic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 1695ef568..4fa04608a 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -37,7 +37,7 @@ def __init__(self, parent=None, scanner_axes=None): super(TiltCorrectionDockWidget, self).__init__(parent) self._n_dim = len(scanner_axes) - self._scan_axes = OrderedDict(sorted(scanner_axes.items())) + self._scan_axes = OrderedDict(scanner_axes.items()) self.setWindowTitle("Tilt Correction") # Create the dock widget contents diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index bf2deae54..5112b3783 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -393,7 +393,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): lin_transform.translate(shift[0], shift[1], shift[2]) self._tilt_corr_transform = lin_transform - self._tilt_corr_axes = [el for idx, el in enumerate(self._scanner().get_constraints().axes) if tilt_axes[idx]] + self._tilt_corr_axes = [el for idx, el in enumerate(OrderedDict(sorted(self._scanner().get_constraints().axes.items()))) if tilt_axes[idx]] def __func_debug_transform(self): From 1143ba1b69ca9870fbb44c453566da6ca3aa8e77 Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 18 Jun 2023 00:31:03 +0200 Subject: [PATCH 41/81] code cleaning --- src/qudi/gui/scanning/scannergui.py | 4 +-- .../scanning/tilt_correction_dcokwidget.py | 26 +------------- src/qudi/logic/scanning_probe_logic.py | 34 +++++++++++++++++-- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 7362d661f..821f73d40 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1093,10 +1093,10 @@ def tilt_corr_support_vector_updated(self): self._mw.action_toggle_tilt_correction.setEnabled(False) if all_vecs_valid: - shift_vec_arr = self.tilt_correction_dockwidget.vector_dict_2_array(support_vecs_val[-1]) + shift_vec_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[-1]) if not np.all([np.isfinite(el) for el in shift_vec_arr]): shift_vec_arr = None - support_vecs_arr = self.tilt_correction_dockwidget.vector_dict_2_array(support_vecs_val[:-1]) + support_vecs_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[:-1]) self._scanning_logic().configure_tilt_correction(support_vecs_arr, shift_vec_arr) self.toggle_switch_widget.setEnabled(True) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 4fa04608a..1c45e0d53 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -37,7 +37,7 @@ def __init__(self, parent=None, scanner_axes=None): super(TiltCorrectionDockWidget, self).__init__(parent) self._n_dim = len(scanner_axes) - self._scan_axes = OrderedDict(scanner_axes.items()) + self._scan_axes = scanner_axes self.setWindowTitle("Tilt Correction") # Create the dock widget contents @@ -121,30 +121,6 @@ def set_support_vector(self, vector, idx): [self.support_vecs_box[idx][idx_ax].setValue(vector[ax]) for idx_ax, ax in dim_idxs] - def vector_dict_2_array(self, vector): - """ - Convert vectors given as dict (with axes keys) to arrays and ensure correct order. - vector: dict or list of dicts - - return: np.array or list of np.array - """ - - if type(vector) != list: - vector = [vector] - - vecs_arr = [] - for vec in vector: - vec_arr = np.ones((len(self._scan_axes)))*np.nan - for idx, ax in enumerate(self._scan_axes): - vec_arr[idx] = vec[ax] - - assert np.all(~np.isnan(vec_arr)) - vecs_arr.append(vec_arr) - - if len(vec_arr) == 1: - return vecs_arr[0] - return vecs_arr - @property def auto_origin(self): return True if self.auto_origin_switch.switch_state == 'ON' else False diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 5112b3783..fa17fd9df 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -116,6 +116,9 @@ def on_activate(self): self.__scan_poll_timer = QtCore.QTimer() self.__scan_poll_timer.setSingleShot(True) self.__scan_poll_timer.timeout.connect(self.__scan_poll_loop, QtCore.Qt.QueuedConnection) + + self._scan_axes = OrderedDict(sorted(self._scanner().get_constraints().axes.items())) + return def on_deactivate(self): @@ -393,8 +396,33 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): lin_transform.translate(shift[0], shift[1], shift[2]) self._tilt_corr_transform = lin_transform - self._tilt_corr_axes = [el for idx, el in enumerate(OrderedDict(sorted(self._scanner().get_constraints().axes.items()))) if tilt_axes[idx]] + self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] + + def tilt_vector_dict_2_array(self, vector, reduced_dim=False): + """ + Convert vectors given as dict (with axes keys) to arrays and ensure correct order. + vector: dict or list of dicts + + return: np.array or list of np.array + """ + + if type(vector) != list: + vector = [vector] + + axes = self._tilt_corr_axes if reduced_dim else self._scan_axes + + vecs_arr = [] + for vec in vector: + vec_arr = np.ones((len(axes)))*np.nan + for idx, ax in enumerate(axes): + vec_arr[idx] = vec[ax] + + assert np.all(~np.isnan(vec_arr)) + vecs_arr.append(vec_arr) + if len(vecs_arr) == 1: + return vecs_arr[0] + return vecs_arr def __func_debug_transform(self): def transform_to(coord, inverse=False): @@ -558,11 +586,11 @@ def __transform_func(self, coord, inverse=False): # convert from coordinate dict to plain vector transform = self._tilt_corr_transform.__call__ - coord_vec = np.asarray(list(coord_reduced.values())).T + coord_vec = self.tilt_vector_dict_2_array(coord_reduced, reduced_dim=True).T coord_vec_transf = transform(coord_vec, invert=inverse).T # make dict again after vector rotation coord_transf = cp.copy(coord) - [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, (ax, val)) in enumerate(coord_reduced.items())] + [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, ax) in enumerate(self._tilt_corr_axes)] #self.log.debug(f"Tranforming {coord} => {coord_transf}") From 310bf5df0d8b6ea79b7b5a3ca53742b9e7e6d63c Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 18 Jun 2023 15:32:03 +0200 Subject: [PATCH 42/81] make tilt correction button a ToggleIconsQAction --- src/qudi/gui/scanning/scannergui.py | 32 +++++++++++++++----------- src/qudi/gui/scanning/ui_scannergui.ui | 14 ----------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 821f73d40..1b916a9cc 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -57,6 +57,11 @@ def __init__(self): # Load it super().__init__() uic.loadUi(ui_file, self) + + self.action_toggle_tilt_correction = ToggleIconsQAction(self, 'Tilt correction', + "./artwork/icons/correct-tilt_toggle.svg", + "./artwork/icons/correct-tilt_toggle_off.svg") + self.util_toolBar.addAction(self.action_toggle_tilt_correction) return def mouseDoubleClickEvent(self, event): @@ -67,13 +72,6 @@ def mouseDoubleClickEvent(self, event): super().mouseDoubleClickEvent(event) return - # Create the tilt correction dock widget - - #tilt_correction_dock_widget = TiltCorrectionDockWidget() - - # Add the dock widget to the main window - #self.addDockWidget(Qt.DockWidgetArea(8), tilt_correction_dock_widget) - class SaveDialog(QtWidgets.QDialog): """ Dialog to provide feedback and block GUI while saving """ def __init__(self, parent, title="Please wait", text="Saving..."): @@ -254,6 +252,7 @@ def on_activate(self): QtCore.Qt.QueuedConnection) self.toggle_switch_widget.toggle_switch.sigStateChanged.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) + self._mw.action_toggle_tilt_correction.triggered.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) [box.valueChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) @@ -1112,11 +1111,18 @@ def toggle_tilt_correction(self, state): self._scanning_logic().toggle_tilt_correction(enabled) - icon_on = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_toggle.svg")) - icon_off = QtGui.QIcon(QtGui.QPixmap("./artwork/icons/correct-tilt_toggle_off.svg")) - if enabled: - self._mw.action_toggle_tilt_correction.setIcon(icon_on) - else: - self._mw.action_toggle_tilt_correction.setIcon(icon_off) +class ToggleIconsQAction(QAction): + + def __init__(self, parent, text, icon_on, icon_off): + self.icon_on = QtGui.QIcon(QtGui.QPixmap(icon_on)) + self.icon_off = QtGui.QIcon(QtGui.QPixmap(icon_off)) + super().__init__(self.icon_off, text, parent, checkable=True) + + self.triggered.connect(self.set_state, QtCore.Qt.QueuedConnection) + def set_state(self, enabled): + if enabled: + self.setIcon(self.icon_on) + else: + self.setIcon(self.icon_off) diff --git a/src/qudi/gui/scanning/ui_scannergui.ui b/src/qudi/gui/scanning/ui_scannergui.ui index eb4ed21d7..6350b216f 100644 --- a/src/qudi/gui/scanning/ui_scannergui.ui +++ b/src/qudi/gui/scanning/ui_scannergui.ui @@ -95,7 +95,6 @@ - @@ -340,19 +339,6 @@ Shortcut:Alt+T Tilt correction - - - true - - - - ../../../../../qudi-core/src/qudi/artwork/icons/correct-tilt_off.svg - - - - Tilt correction - - From 6e6a4bbff421e01195a100c2acff3673803a37cb Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 18 Jun 2023 15:57:34 +0200 Subject: [PATCH 43/81] correct shift vector rotation --- src/qudi/logic/scanning_probe_logic.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index fa17fd9df..cf5179bec 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -392,8 +392,12 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): shift = shift_vec lin_transform = LinearTransformation3D() + shift_vec_transform = LinearTransformation3D() + lin_transform.translate(-shift[0], -shift[1], -shift[2]) lin_transform.add_rotation(rot_mat) - lin_transform.translate(shift[0], shift[1], shift[2]) + shift_vec_transform.add_rotation(rot_mat) + shift_back = shift_vec_transform(shift) + lin_transform.translate(shift_back[0], shift_back[1], shift_back[2]) self._tilt_corr_transform = lin_transform self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] From 84fb7bbe60aa0c1a6e6d69cce36fed6db5aa2053 Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 18 Jun 2023 16:44:52 +0200 Subject: [PATCH 44/81] reverse unwanted changes in unrelated files --- src/qudi/gui/pulsed/pulsed_maingui.py | 7 --- src/qudi/hardware/awg/keysight_m819x.py | 45 ++++++------------- .../hardware/fastcomtec/fastcomtecmcs6.py | 7 +-- 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/src/qudi/gui/pulsed/pulsed_maingui.py b/src/qudi/gui/pulsed/pulsed_maingui.py index afb995873..7934c55ff 100644 --- a/src/qudi/gui/pulsed/pulsed_maingui.py +++ b/src/qudi/gui/pulsed/pulsed_maingui.py @@ -3170,7 +3170,6 @@ def update_laser_data(self): @return: """ - self._sanatize_laser_index() laser_index = self._pe.laserpulses_ComboBox.currentIndex() show_raw = self._pe.laserpulses_display_raw_CheckBox.isChecked() is_gated = len(self.pulsedmasterlogic().raw_data.shape) > 1 @@ -3205,12 +3204,6 @@ def slot(): self.generate_predefined_clicked(method_name, sample_and_load) return slot - def _sanatize_laser_index(self): - laser_index = self._pe.laserpulses_ComboBox.currentIndex() - if laser_index not in range(len(self.pulsedmasterlogic().laser_data)): - laser_index = 0 - self._pe.laserpulses_ComboBox.setCurrentIndex(laser_index) - @QtCore.Slot() def run_pg_benchmark(self): diff --git a/src/qudi/hardware/awg/keysight_m819x.py b/src/qudi/hardware/awg/keysight_m819x.py index 4efac2ba3..93c363a1a 100644 --- a/src/qudi/hardware/awg/keysight_m819x.py +++ b/src/qudi/hardware/awg/keysight_m819x.py @@ -63,12 +63,9 @@ class AWGM819X(PulserInterface): _wave_mem_mode = None _wave_file_extension = '.bin' _wave_transfer_datatype = 'h' - _dynamic_sequence_mode = ConfigOption(name='dynamic_sequence_mode', default=True, missing='nothing') # explicitly set low/high levels for [[d_ch1_low, d_ch1_high], [d_ch2_low, d_ch2_high], ...] _d_ch_level_low_high = ConfigOption(name='d_ch_level_low_high', default=[], missing='nothing') - _ext_ref_clock_freq = ConfigOption(name='ext_ref_clock_freq', default=None, missing='nothing') - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -182,7 +179,6 @@ def pulser_on(self): class variable status_dic.) """ self._write_output_on() - self.check_dev_error() # Sec. 6.4 from manual: # In the program it is recommended to send the command for starting @@ -324,8 +320,7 @@ def load_sequence(self, sequence_name): select the first segment in your sequence, before any dynamic sequence selection. """ self.write_all_ch(":STAB{}:SEQ:SEL 0", all_by_one={'m8195a': True}) - if self._dynamic_sequence_mode: - self.write_all_ch(":STAB{}:DYN ON", all_by_one={'m8195a': True}) + self.write_all_ch(":STAB{}:DYN ON", all_by_one={'m8195a': True}) return 0 @@ -932,12 +927,12 @@ def write_sequence(self, name, sequence_parameters): control = self._get_sequence_control_bin(sequence_parameters, index) - sequence_loop_count = 1 + seq_loop_count = 1 if seq_step.repetitions == -1: # this is ugly, limits maximal waiting time. 1 Sa -> approx. 0.3 s - segment_loop_count = 4294967295 # max value, todo: from constraints + seg_loop_count = 4294967295 # max value, todo: from constraints else: - segment_loop_count = seq_step.repetitions + 1 # if repetitions = 0 then do it once + seg_loop_count = seq_step.repetitions + 1 # if repetitions = 0 then do it once seg_start_offset = 0 # play whole segement from start... seg_end_offset = 0xFFFFFFFF # to end @@ -957,8 +952,8 @@ def write_sequence(self, name, sequence_parameters): self.write(':STAB:DATA {0}, {1}, {2}, {3}, {4}, {5}, {6}' .format(index, control, - sequence_loop_count, - segment_loop_count, + seq_loop_count, + seg_loop_count, segment_id_ch1, seg_start_offset, seg_end_offset)) @@ -966,8 +961,8 @@ def write_sequence(self, name, sequence_parameters): self.write(':STAB2:DATA {0}, {1}, {2}, {3}, {4}, {5}, {6}' .format(index, control, - sequence_loop_count, - segment_loop_count, + seq_loop_count, + seg_loop_count, segment_id_ch2, seg_start_offset, seg_end_offset)) @@ -1214,7 +1209,8 @@ def _init_device(self): self.reset() constr = self.get_constraints() - self._set_ref_clock() + self.write(':ROSC:SOUR INT') # Chose source for reference clock + self._set_awg_mode() # General procedure according to Sec. 8.22.6 in AWG8190A manual: @@ -1247,15 +1243,6 @@ def _init_device(self): self._segment_table = [[], []] # [0]: ch1, [1]: ch2. Local, read-only copy of the device segment table self._flag_segment_table_req_update = True # local copy requires update - def _set_ref_clock(self): - if self._ext_ref_clock_freq is None: - self.write(':ROSC:SOUR INT') - else: - self._ext_ref_clock_freq = int(self._ext_ref_clock_freq) - self.write(':ROSC:SOUR EXT') - self.write(f':ROSC:FREQ {self._ext_ref_clock_freq:d}') - self.log.debug(f"Setting to external ref clock with f= {self._ext_ref_clock_freq/1e6} MHz") - def _load_list_2_dict(self, load_dict): def _create_load_dict_allch(load_dict): @@ -1385,9 +1372,9 @@ def check_dev_error(self): for i in range(30): # error buffer of device is 30 raw_str = self.query(':SYST:ERR?', force_no_check=True) - is_error = not ('0,' == raw_str[0:2]) + is_error = not ('0' in raw_str[0]) if is_error: - self.log.warning("AWG issued error: {}".format(raw_str)) + self.log.warn("AWG issued error: {}".format(raw_str)) has_error_occured = True else: break @@ -2202,9 +2189,9 @@ def get_constraints(self): # manual 1.5.4: Depending on the Sample Rate Divider, the 256 sample wide output of the sequencer # is divided by 1, 2 or 4. constraints.waveform_length.step = 256 / self._sample_rate_div - constraints.waveform_length.min = 1280 / self._sample_rate_div # != p 108 manual, but tested manually ('MARK') + constraints.waveform_length.min = 1280 # != p 108 manual, but tested manually ('MARK') constraints.waveform_length.max = int(16e9) - constraints.waveform_length.default = 1280 / self._sample_rate_div + constraints.waveform_length.default = 1280 # analog channel constraints.a_ch_amplitude.min = 0.075 # from soft frontpanel @@ -2833,10 +2820,6 @@ def _get_sequence_control_bin(self, sequence_parameters, idx_step): if 'pattern_jump_address' in next_step: control = 0x1 << 30 - if 'segment_advance_mode' in seq_step: - if seq_step['segment_advance_mode'] == 'conditional': - control += 0x1 << 16 - control += 0x1 << 24 # always enable markers return control diff --git a/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py b/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py index 5f841efc2..5695209d4 100644 --- a/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py +++ b/src/qudi/hardware/fastcomtec/fastcomtecmcs6.py @@ -704,12 +704,7 @@ def set_cycle_mode(self, sequential_mode=True, cycles=None): # Turn on or off sequential cycle mode if sequential_mode: - self.log.debug("Sequential mode enabled. Make sure to set 'checksync=0' in mcs6a.ini. " - "Resyncing can cause issues in sequential mode on some driver versions.") - # old settings (dec=1978500) + disable "sweep counter not needed" + disable "allow 6 byte words" - raw_bytes_dec = 35528836 - cmd = 'sweepmode={0}'.format(hex(raw_bytes_dec)) - self.log.debug(f"Sweepmode set to: {raw_bytes_dec}") + cmd = 'sweepmode={0}'.format(hex(1978500)) else: cmd = 'sweepmode={0}'.format(hex(1978496)) self.dll.RunCmd(0, bytes(cmd, 'ascii')) From 7db3d5570db0e0a9642053f5038cde85c051fc22 Mon Sep 17 00:00:00 2001 From: NicolasStaudenmaier Date: Tue, 4 Jul 2023 11:14:29 +0200 Subject: [PATCH 45/81] testing, seems like tilt correction works incl origin --- src/qudi/logic/scanning_probe_logic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index cf5179bec..f6faaa313 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -393,11 +393,16 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): lin_transform = LinearTransformation3D() shift_vec_transform = LinearTransformation3D() - lin_transform.translate(-shift[0], -shift[1], -shift[2]) + lin_transform.add_rotation(rot_mat) + lin_transform.translate(shift[0], shift[1], shift[2]) + shift_vec_transform.add_rotation(rot_mat) - shift_back = shift_vec_transform(shift) + shift_back = shift_vec_transform(-shift) + lin_transform.translate(shift_back[0], shift_back[1], shift_back[2]) + #self.log.debug(f"Shift vec {shift}, shift back {shift_back}") + #self.log.debug(f"Matrix: {lin_transform.matrix}") self._tilt_corr_transform = lin_transform self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] From b3c88244bb994d6efbcb38c3b2c771afbb028bd5 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 11:25:52 +0200 Subject: [PATCH 46/81] remove slider widget for toggling tilt correction in confocal toolbar, use QAction instead --- .../scanning/Tilt_correction_toggle_switch.py | 42 ------------------- src/qudi/gui/scanning/scannergui.py | 31 +++----------- 2 files changed, 5 insertions(+), 68 deletions(-) delete mode 100644 src/qudi/gui/scanning/Tilt_correction_toggle_switch.py diff --git a/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py b/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py deleted file mode 100644 index 16a168988..000000000 --- a/src/qudi/gui/scanning/Tilt_correction_toggle_switch.py +++ /dev/null @@ -1,42 +0,0 @@ -from PySide2.QtWidgets import QWidget, QLabel, QVBoxLayout,QCheckBox -from PySide2.QtCore import Qt, Signal -from PySide2.QtGui import QPalette, QColor - - -class ActivateTiltCorrection(QWidget): - stateChanged = Signal(bool) - - def __init__(self): - super().__init__() - - # Set up the UI for the toggle switch - self.initUI() - - def initUI(self): - # Create a label for the toggle switch - self.label = QLabel() - self.label.setAlignment(Qt.AlignCenter) - self.label.setStyleSheet("QLabel { background-color: red; color: white; }") - self.label.setFixedSize(100, 40) - self.label.setText("Tilt_correction:OFF") - - # Create a vertical layout for the toggle switch - layout = QVBoxLayout() - layout.addWidget(self.label) - - # Set the layout for the widget - self.setLayout(layout) - - def mousePressEvent(self, event): - # Toggle the state of the toggle switch - if self.label.text() == "Tilt_correction:OFF": - self.label.setText("Tilt_correction:ON") - self.label.setStyleSheet("QLabel { background-color: green; color: white; }") - self.stateChanged.emit(True) # Emit signal for state change - else: - self.label.setText("Tilt_correction:OFF") - self.label.setStyleSheet("QLabel { background-color: red; color: white; }") - self.stateChanged.emit(False) # Emit signal for state change - - def isChecked(self): - return self.label.text() == "Tilt_correction:ON" \ No newline at end of file diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 1b916a9cc..459fefd2e 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -36,14 +36,13 @@ from qudi.core.module import GuiBase from qudi.logic.scanning_optimize_logic import OptimizerScanSequence from qudi.gui.scanning.tilt_correction_dcokwidget import TiltCorrectionDockWidget -from qudi.gui.scanning.Tilt_correction_toggle_switch import ActivateTiltCorrection from qudi.gui.scanning.axes_control_dockwidget import AxesControlDockWidget from qudi.gui.scanning.optimizer_setting_dialog import OptimizerSettingDialog from qudi.gui.scanning.scan_settings_dialog import ScannerSettingDialog from qudi.gui.scanning.scan_dockwidget import ScanDockWidget from qudi.gui.scanning.optimizer_dockwidget import OptimizerDockWidget from qudi.util.widgets.toggle_switch import ToggleSwitch -from qudi.gui.switch.switch_state_widgets import SwitchRadioButtonWidget, ToggleSwitchWidget +from qudi.gui.switch.switch_state_widgets import SwitchRadioButtonWidget class ConfocalMainWindow(QtWidgets.QMainWindow): @@ -250,9 +249,6 @@ def on_activate(self): QtCore.Qt.QueuedConnection) tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) - self.toggle_switch_widget.toggle_switch.sigStateChanged.connect(self.toggle_tilt_correction, - QtCore.Qt.QueuedConnection) - self._mw.action_toggle_tilt_correction.triggered.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) [box.valueChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) @@ -310,7 +306,6 @@ def on_deactivate(self): for scan in tuple(self.scan_2d_dockwidgets): self._remove_scan_dockwidget(scan) - self.toggle_switch_widget.toggle_switch.sigStateChanged.disconnect() tilt_widget = self.tilt_correction_dockwidget tilt_widget.tilt_set_01_pushButton.clicked.disconnect() tilt_widget.tilt_set_02_pushButton.clicked.disconnect() @@ -408,15 +403,6 @@ def _init_static_dockwidgets(self): self.optimizer_dockwidget.visibilityChanged.connect(self._mw.action_view_optimizer.setChecked) self._mw.action_view_optimizer.triggered[bool].connect(self.optimizer_dockwidget.setVisible) - # Create a ToggleSwitchWidget - self.toggle_switch_widget = ToggleSwitchWidget(switch_states=('Tilt_Correction:OFF', - 'Tilt_Correction:ON')) - - # Set size policy for the ToggleSwitchWidget - self.toggle_switch_widget.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - #toggle_switch_widget.setStyleSheet("QToolButton { height: 20px; width: 80px; }") - - self._mw.util_toolBar.addWidget(self.toggle_switch_widget) self._mw.util_toolBar.visibilityChanged.connect(self._mw.action_view_toolbar.setChecked) self._mw.action_view_toolbar.triggered[bool].connect(self._mw.util_toolBar.setVisible) @@ -1087,8 +1073,6 @@ def tilt_corr_support_vector_updated(self): self.toggle_tilt_correction(False) self._scanning_logic().configure_tilt_correction(None, None) - self.toggle_switch_widget.set_state('Tilt_Correction:OFF') - self.toggle_switch_widget.setEnabled(False) self._mw.action_toggle_tilt_correction.setEnabled(False) if all_vecs_valid: @@ -1098,18 +1082,13 @@ def tilt_corr_support_vector_updated(self): support_vecs_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[:-1]) self._scanning_logic().configure_tilt_correction(support_vecs_arr, shift_vec_arr) - self.toggle_switch_widget.setEnabled(True) self._mw.action_toggle_tilt_correction.setEnabled(True) def toggle_tilt_correction(self, state): - # toggle switch - if type(state) == str: - enabled = True if state == 'Tilt_Correction:ON' else False - # button - if type(state) == bool: - enabled = state - - self._scanning_logic().toggle_tilt_correction(enabled) + if type(state) != bool: + raise ValueError + + self._scanning_logic().toggle_tilt_correction(state) class ToggleIconsQAction(QAction): From b6370a25bc8ca84ccaf6e0c32950cbf2c1929fc9 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 11:50:25 +0200 Subject: [PATCH 47/81] fix warning on missing objectName --- src/qudi/gui/scanning/tilt_correction_dcokwidget.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py index 1c45e0d53..f702a977b 100644 --- a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dcokwidget.py @@ -28,18 +28,15 @@ from PySide2.QtWidgets import QDockWidget, QWidget,QGridLayout, QLabel, QPushButton,QTableWidget from qudi.util.widgets.scientific_spinbox import ScienDSpinBox from qudi.gui.switch.switch_state_widgets import SwitchRadioButtonWidget, ToggleSwitchWidget -#from qudi.interface.scanning_probe_interface import ScanData -#from qudi.gui.scanning.scan_dockwidget import ScanDockWidget class TiltCorrectionDockWidget(QDockWidget): - def __init__(self, parent=None, scanner_axes=None): - super(TiltCorrectionDockWidget, self).__init__(parent) + def __init__(self, scanner_axes=None, **kwargs): + super().__init__('Tilt Correction', objectName='Tilt Correction', **kwargs) self._n_dim = len(scanner_axes) self._scan_axes = scanner_axes - self.setWindowTitle("Tilt Correction") # Create the dock widget contents dock_widget_contents = QWidget() dock_widget_layout = QGridLayout(dock_widget_contents) @@ -64,10 +61,9 @@ def __init__(self, parent=None, scanner_axes=None): self.tilt_set_03_pushButton.setMaximumSize(70, 16777215) dock_widget_layout.addWidget(self.tilt_set_03_pushButton, 3, 0) - origin_switch_label = QLabel("Auto origin") + origin_switch_label = QLabel("Auto rotation origin") dock_widget_layout.addWidget(origin_switch_label, 4, 0) self.auto_origin_switch = ToggleSwitchWidget(switch_states=('OFF', 'ON')) - # todo: disconnect self.auto_origin_switch.toggle_switch.sigStateChanged self.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.auto_origin_changed, QtCore.Qt.QueuedConnection) self.auto_origin_switch.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) From 13dcbda18a11f4cec6e2d8d178565b5414bd171b Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 11:54:27 +0200 Subject: [PATCH 48/81] rename file name typo --- ...ilt_correction_dcokwidget.py => tilt_correction_dockwidget.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/qudi/gui/scanning/{tilt_correction_dcokwidget.py => tilt_correction_dockwidget.py} (100%) diff --git a/src/qudi/gui/scanning/tilt_correction_dcokwidget.py b/src/qudi/gui/scanning/tilt_correction_dockwidget.py similarity index 100% rename from src/qudi/gui/scanning/tilt_correction_dcokwidget.py rename to src/qudi/gui/scanning/tilt_correction_dockwidget.py From f30414a3465c660b629e73df1eb0376a38b66788 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 13:43:40 +0200 Subject: [PATCH 49/81] basic infrastructure to save tilt correction data --- .../hardware/dummy/scanning_probe_dummy.py | 6 ++--- .../interface/scanning_probe_interface.py | 22 +++++++++++++++++++ src/qudi/logic/scanning_data_logic.py | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 2aaabf596..2c7f09360 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -320,7 +320,7 @@ def get_target(self): @return dict: current target position per axis. """ with self._thread_lock: - self.log.debug('Scanning probe dummy "get_target" called.') + #self.log.debug('Scanning probe dummy "get_target" called.') return self._current_position.copy() def get_position(self): @@ -329,7 +329,7 @@ def get_position(self): @return dict: current target position per axis. """ with self._thread_lock: - self.log.debug('Scanning probe dummy "get_position" called.') + #self.log.debug('Scanning probe dummy "get_position" called.') position = {ax: pos + np.random.normal(0, self._position_accuracy[ax]) for ax, pos in self._current_position.items()} return position @@ -447,7 +447,7 @@ def get_scan_data(self): # if self.thread() is not QtCore.QThread.currentThread(): # self.log.debug('Scanning probe dummy "get_scan_data" called.') if self._scan_data is None: - print('nope, no scan data in hardware') + self.log.debug('No scan data in hardware, returning None') return None if self.module_state() != 'idle': diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index c5b78d801..e308c69ac 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -250,6 +250,7 @@ def __init__(self, channels, scan_axes, scan_range, scan_resolution, scan_freque self._data = None self._position_data = None self._target_at_start = target_at_start + self._tilt_correction_info = {'enabled': False} # TODO: Automatic interpolation onto rectangular grid needs to be implemented (for position feedback HW) return @@ -414,6 +415,14 @@ def from_dict(cls, dict_repr): new_inst._timestamp = datetime.datetime.fromtimestamp(dict_repr['timestamp']) return new_inst + @property + def tilt_correction_info(self): + return self._tilt_correction_info + + @tilt_correction_info.setter + def tilt_correction_info(self, info_dict): + self._tilt_correction_info = info_dict + class ScannerChannel: """ @@ -667,3 +676,16 @@ def get_target(self): def get_position(self): return self.coordinate_transform(super().get_position(), inverse=True) + + def get_scan_data(self): + + scan_data = super().get_scan_data() + if scan_data: + # todo: transform_func not needed, replace with valuable info (matrix, angle, ..) + tilt_info = {'enabled': self.coordinate_transform_enabled, + 'transform_func': self._coordinate_transform} + scan_data.tilt_correction_info = tilt_info + + self.log.debug(f"Get scan data for titl corrected hw called. Info: {tilt_info}") + + return scan_data \ No newline at end of file diff --git a/src/qudi/logic/scanning_data_logic.py b/src/qudi/logic/scanning_data_logic.py index e41d63486..50df29d7c 100644 --- a/src/qudi/logic/scanning_data_logic.py +++ b/src/qudi/logic/scanning_data_logic.py @@ -313,6 +313,7 @@ def save_scan(self, scan_data, color_range=None): parameters["pixel frequency"] = scan_data.scan_frequency parameters[f"scanner target at start"] = scan_data.scanner_target_at_start parameters['measurement start'] = str(scan_data._timestamp) + parameters['tilt correction info'] = scan_data.tilt_correction_info # add meta data for axes in full target, but not scan axes if scan_data.scanner_target_at_start: From 7c3b05158b873877483f2abdf38d5ab3f5f141f0 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 14:58:51 +0200 Subject: [PATCH 50/81] fix import error after renaming --- src/qudi/gui/scanning/scannergui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 459fefd2e..0446230f1 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -35,7 +35,7 @@ from qudi.interface.scanning_probe_interface import ScanData from qudi.core.module import GuiBase from qudi.logic.scanning_optimize_logic import OptimizerScanSequence -from qudi.gui.scanning.tilt_correction_dcokwidget import TiltCorrectionDockWidget +from qudi.gui.scanning.tilt_correction_dockwidget import TiltCorrectionDockWidget from qudi.gui.scanning.axes_control_dockwidget import AxesControlDockWidget from qudi.gui.scanning.optimizer_setting_dialog import OptimizerSettingDialog from qudi.gui.scanning.scan_settings_dialog import ScannerSettingDialog From de20c2f27cda0cff4cfe06709172170a064ba478 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 15:48:22 +0200 Subject: [PATCH 51/81] fix broken conversion of tilt vectors given as dict of arrays --- .../hardware/dummy/scanning_probe_dummy.py | 9 ++++-- src/qudi/logic/scanning_probe_logic.py | 28 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 2c7f09360..9efa0b2cd 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -527,13 +527,16 @@ def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): class ScanningProbeDummyCorrected(CoordinateTransformMixin, ScanningProbeDummy): def _init_scan_grid(self, x_values, y_values): - # todo: this is demonstration only, not really a transformation + # todo: this is fake transformation, as only 2 coordinates of the scan_grid are taken - vectors = {'x': x_values, 'y': y_values, 'z': np.zeros(len(x_values))} + vectors = {'x': x_values, 'y': y_values} + vectors = self._expand_coordinate(vectors) vectors_tilted = self.coordinate_transform(vectors) grid = np.meshgrid(vectors_tilted['x'], vectors_tilted['y'], indexing='ij') - print(f"Transforming grid: {grid}") + + if self.coordinate_transform_enabled: + self.log.debug(f"Transforming scan grid: {grid}") return grid diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index f6faaa313..8bbc4add9 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -410,23 +410,28 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): def tilt_vector_dict_2_array(self, vector, reduced_dim=False): """ Convert vectors given as dict (with axes keys) to arrays and ensure correct order. - vector: dict or list of dicts + vector: dict (single coord or arrays per key) or list of dicts return: np.array or list of np.array """ - if type(vector) != list: - vector = [vector] + axes = self._tilt_corr_axes if reduced_dim else self._scan_axes.keys() - axes = self._tilt_corr_axes if reduced_dim else self._scan_axes + if type(vector) != list: + vectors = [vector] + else: + vectors = vector vecs_arr = [] - for vec in vector: - vec_arr = np.ones((len(axes)))*np.nan - for idx, ax in enumerate(axes): - vec_arr[idx] = vec[ax] + for vec in vectors: + if not isinstance(vec, dict): + raise ValueError + + # vec_sorted dict has correct order (defined by order in axes). Then converted to array + vec_sorted = {ax: np.nan for ax in axes} + vec_sorted.update(vec) + vec_arr = np.asarray(list(vec_sorted.values())) - assert np.all(~np.isnan(vec_arr)) vecs_arr.append(vec_arr) if len(vecs_arr) == 1: @@ -588,10 +593,9 @@ def __transform_func(self, coord, inverse=False): :param inverse: :return: """ - # todo: double check: reduce dimension to 3d + coord_reduced = {key:val for key, val in list(coord.items())[:3] if key in self._tilt_corr_axes} coord_reduced = OrderedDict(sorted(coord_reduced.items())) - #ax_2_idx_map = {key:idx for (idx, (key, val)) in enumerate(list(coord.items())) if key in self._tilt_corr_axes} # convert from coordinate dict to plain vector transform = self._tilt_corr_transform.__call__ @@ -601,6 +605,4 @@ def __transform_func(self, coord, inverse=False): coord_transf = cp.copy(coord) [coord_transf.update({ax: coord_vec_transf[idx]}) for (idx, ax) in enumerate(self._tilt_corr_axes)] - #self.log.debug(f"Tranforming {coord} => {coord_transf}") - return coord_transf From 20d13adcaa78ac3684f813e52496550cc3457aaa Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jul 2023 16:32:25 +0200 Subject: [PATCH 52/81] update button on disabled tilt correction --- src/qudi/gui/scanning/scannergui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 0446230f1..af2dbe9bd 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1089,6 +1089,8 @@ def toggle_tilt_correction(self, state): raise ValueError self._scanning_logic().toggle_tilt_correction(state) + self._mw.action_toggle_tilt_correction.set_state(state) + self._mw.action_toggle_tilt_correction.setChecked(state) class ToggleIconsQAction(QAction): From 070f29f895b5b238e9fd1e1cc8a19078d81a4d86 Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 6 Aug 2023 13:13:03 +0200 Subject: [PATCH 53/81] save coord transform matrix in all scanners using CoordinateTransformMixin --- src/qudi/interface/scanning_probe_interface.py | 14 ++++++++++---- src/qudi/logic/scanning_probe_logic.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index e308c69ac..cc8004075 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -34,13 +34,14 @@ class ScanningProbeInterface(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._coordinate_transform = None + self._coordinate_transform_matrix = None def coordinate_transform(self, val, inverse=False): if self._coordinate_transform is None: return val return self._coordinate_transform(val, inverse) - def set_coordinate_transform(self, transform_func): + def set_coordinate_transform(self, transform_func, transform_matrix=None): if transform_func is not None: raise ValueError('Coordinate transformation not supported by scanning hardware.') @@ -656,12 +657,13 @@ class CoordinateTransformMixin: MyTransformationScanner(CoordinateTransformMixin, MyScanner): pass """ - def set_coordinate_transform(self, transform_func): + def set_coordinate_transform(self, transform_func, transform_matrix=None): # ToDo: Proper sanity checking here, e.g. function signature etc. if transform_func is not None and not callable(transform_func): raise ValueError('Coordinate transformation function must be callable with ' 'signature "coordinate_transform(value, inverse=False)"') self._coordinate_transform = transform_func + self._coordinate_transform_matrix = transform_matrix def move_absolute(self, position, velocity=None, blocking=False): new_pos_bare = super().move_absolute(self.coordinate_transform(position), velocity, blocking) @@ -683,9 +685,13 @@ def get_scan_data(self): if scan_data: # todo: transform_func not needed, replace with valuable info (matrix, angle, ..) tilt_info = {'enabled': self.coordinate_transform_enabled, - 'transform_func': self._coordinate_transform} - scan_data.tilt_correction_info = tilt_info + } + if self.coordinate_transform_enabled: + transform_info = {'transform_func': self._coordinate_transform, + 'tansform_matrix': self._coordinate_transform_matrix.matrix} + tilt_info.update(transform_info) + scan_data.tilt_correction_info = tilt_info self.log.debug(f"Get scan data for titl corrected hw called. Info: {tilt_info}") return scan_data \ No newline at end of file diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 8bbc4add9..960c9040b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -352,7 +352,7 @@ def toggle_tilt_correction(self, enable=True, debug_func=False): func = self.__transform_func if self._tilt_corr_transform else None if enable: - self._scanner().set_coordinate_transform(func) + self._scanner().set_coordinate_transform(func, self._tilt_corr_transform) else: self._scanner().set_coordinate_transform(None) From b23f78649648e3a95d2affc5e51462f18614639c Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 6 Aug 2023 14:03:36 +0200 Subject: [PATCH 54/81] clean up supports_coordinate_transform() by making use of mixin --- .../hardware/dummy/scanning_probe_dummy.py | 3 +-- .../interfuse/ni_scanning_probe_interfuse.py | 3 +-- .../interface/scanning_probe_interface.py | 27 ++++++++----------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 9efa0b2cd..bf8222305 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -127,8 +127,7 @@ def on_activate(self): channels=channels, backscan_configurable=False, has_position_feedback=False, - square_px_only=False, - allow_coordinate_transform=self.supports_coordinate_transform) + square_px_only=False) self.__scan_start = 0 self.__last_line = -1 self.__update_timer = QtCore.QTimer() diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 3f06f9105..7b74e262e 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -160,8 +160,7 @@ def on_activate(self): channels=channels, backscan_configurable=False, # TODO incorporate in scanning_probe toolchain has_position_feedback=False, # TODO incorporate in scanning_probe toolchain - square_px_only=False, - allow_coordinate_transform=self.supports_coordinate_transform) # TODO incorporate in scanning_probe toolchain + square_px_only=False) # self._target_pos = self._get_position() # get voltages/pos from ni_ao self._toggle_ao_setpoint_channels(False) # And free ao resources after that diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index cc8004075..7bf86e048 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -51,17 +51,7 @@ def coordinate_transform_enabled(self): @property def supports_coordinate_transform(self): - # todo: this should be part of the scanconstraints - # however this function is general for every scanner, but can't live - # in ScanContraints, as no handle to set_coordinate_transform - flag = False - try: - self.set_coordinate_transform(lambda x, inverse: x) - self.set_coordinate_transform(None) - flag = True - except ValueError: - pass - return flag + return self.get_constraints().allow_coordinate_transform @abstractmethod def get_constraints(self): @@ -599,7 +589,7 @@ class ScanConstraints: """ def __init__(self, axes, channels, backscan_configurable, has_position_feedback, - square_px_only, allow_coordinate_transform): + square_px_only): """ """ if not all(isinstance(ax, ScannerAxis) for ax in axes): @@ -612,15 +602,13 @@ def __init__(self, axes, channels, backscan_configurable, has_position_feedback, raise TypeError('Parameter "has_position_feedback" must be of type bool.') if not isinstance(square_px_only, bool): raise TypeError('Parameter "square_px_only" must be of type bool.') - if not isinstance(allow_coordinate_transform, bool): - raise TypeError('Parameter "allow_coordinate_transform" must be of type bool.') + self._axes = {ax.name: ax for ax in axes} self._channels = {ch.name: ch for ch in channels} self._backscan_configurable = bool(backscan_configurable) self._has_position_feedback = bool(has_position_feedback) self._square_px_only = bool(square_px_only) - # todo: do we need this in the ScanContraints or is in scan interface enough? - self._allow_coordinate_transform = bool(allow_coordinate_transform) + self._allow_coordinate_transform = False # overwritten in CoordinateTransformMixin @property def axes(self): @@ -657,6 +645,13 @@ class CoordinateTransformMixin: MyTransformationScanner(CoordinateTransformMixin, MyScanner): pass """ + + def get_constraints(self): + constr = super().get_constraints() + constr._allow_coordinate_transform = True + + return constr + def set_coordinate_transform(self, transform_func, transform_matrix=None): # ToDo: Proper sanity checking here, e.g. function signature etc. if transform_func is not None and not callable(transform_func): From f55bce52b34f6ae1cb0f604d1cc57aaaa955db33 Mon Sep 17 00:00:00 2001 From: timoML Date: Sun, 6 Aug 2023 14:06:56 +0200 Subject: [PATCH 55/81] variable renaming --- src/qudi/interface/scanning_probe_interface.py | 16 ++++++++-------- src/qudi/logic/scanning_data_logic.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 7bf86e048..02ad7e897 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -241,7 +241,7 @@ def __init__(self, channels, scan_axes, scan_range, scan_resolution, scan_freque self._data = None self._position_data = None self._target_at_start = target_at_start - self._tilt_correction_info = {'enabled': False} + self._coord_transform_info = {'enabled': False} # TODO: Automatic interpolation onto rectangular grid needs to be implemented (for position feedback HW) return @@ -407,12 +407,12 @@ def from_dict(cls, dict_repr): return new_inst @property - def tilt_correction_info(self): - return self._tilt_correction_info + def coord_transform_info(self): + return self._coord_transform_info - @tilt_correction_info.setter - def tilt_correction_info(self, info_dict): - self._tilt_correction_info = info_dict + @coord_transform_info.setter + def coord_transform_info(self, info_dict): + self._coord_transform_info = info_dict class ScannerChannel: @@ -683,10 +683,10 @@ def get_scan_data(self): } if self.coordinate_transform_enabled: transform_info = {'transform_func': self._coordinate_transform, - 'tansform_matrix': self._coordinate_transform_matrix.matrix} + 'tansform_matrix': self._coordinate_transform_matrix.matrix} tilt_info.update(transform_info) - scan_data.tilt_correction_info = tilt_info + scan_data.coord_transform_info = tilt_info self.log.debug(f"Get scan data for titl corrected hw called. Info: {tilt_info}") return scan_data \ No newline at end of file diff --git a/src/qudi/logic/scanning_data_logic.py b/src/qudi/logic/scanning_data_logic.py index 50df29d7c..fd5c3a4c4 100644 --- a/src/qudi/logic/scanning_data_logic.py +++ b/src/qudi/logic/scanning_data_logic.py @@ -313,7 +313,7 @@ def save_scan(self, scan_data, color_range=None): parameters["pixel frequency"] = scan_data.scan_frequency parameters[f"scanner target at start"] = scan_data.scanner_target_at_start parameters['measurement start'] = str(scan_data._timestamp) - parameters['tilt correction info'] = scan_data.tilt_correction_info + parameters['coordinate transform info'] = scan_data.coord_transform_info # add meta data for axes in full target, but not scan axes if scan_data.scanner_target_at_start: From d21574bfcea1a0b7362618e2a57b3e4cb601e1d4 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 28 Aug 2023 10:47:02 +0200 Subject: [PATCH 56/81] fix repeatedly scanning with tilt correction leading to shifting z position by _move_absolute() in bare hw coordinates --- .../interfuse/ni_scanning_probe_interfuse.py | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 7b74e262e..0b51f4ab8 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -303,27 +303,7 @@ def move_absolute(self, position, velocity=None, blocking=False): Log error and return current target position if something fails or a scan is in progress. """ - # assert not self.is_running, 'Cannot move the scanner while, scan is running' - if self.is_scan_running: - self.log.error('Cannot move the scanner while, scan is running') - return self._get_target() - - if not set(position).issubset(self.get_constraints().axes): - self.log.error('Invalid axes name in position') - return self._get_target() - - try: - self._prepare_movement(position, velocity=velocity) - - self.__start_ao_write_timer() - if blocking: - self.__wait_on_move_done() - - self._t_last_move = time.perf_counter() - - return self._get_target() - except: - self.log.exception("Couldn't move: ") + return self._move_absolute(position, velocity, blocking) def __wait_on_move_done(self): try: @@ -404,6 +384,7 @@ def _start_scan(self): self._scan_data.new_scan() #self.log.debug(f"New scan data: {self._scan_data.data}, position {self._scan_data._position_data}") self._stored_target_pos = self._get_target().copy() + self.log.debug(f"Target pos at scan start: {self._stored_target_pos}") self._scan_data.scanner_target_at_start = self._stored_target_pos # todo: scanning_probe_logic exits when scanner not locked right away @@ -450,7 +431,8 @@ def _stop_scan(self): self.module_state.unlock() # self.log.debug("Module unlocked") - self.move_absolute(self._stored_target_pos) + self.log.debug(f"Finished scan, move to stored target: {self._stored_target_pos}") + self._move_absolute(self._stored_target_pos) self._stored_target_pos = dict() def get_scan_data(self): @@ -859,6 +841,33 @@ def _get_target(self): else: return self._target_pos + def _move_absolute(self, position, velocity=None, blocking=False): + """ Move to raw target position of the scanner hardware. Interface method .move absolute() might + get wrapped by tilt correction and transform to a virtual coord system. + + """ + # assert not self.is_running, 'Cannot move the scanner while, scan is running' + if self.is_scan_running: + self.log.error('Cannot move the scanner while, scan is running') + return self._get_target() + + if not set(position).issubset(self.get_constraints().axes): + self.log.error('Invalid axes name in position') + return self._get_target() + + try: + self._prepare_movement(position, velocity=velocity) + + self.__start_ao_write_timer() + if blocking: + self.__wait_on_move_done() + + self._t_last_move = time.perf_counter() + + return self._get_target() + except: + self.log.exception("Couldn't move: ") + def __init_ao_timer(self): self.__ni_ao_write_timer = QtCore.QTimer(parent=self) From 7b3644b056379d13e50ce79a2692b14433a3af5e Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 28 Aug 2023 10:57:53 +0200 Subject: [PATCH 57/81] disable tilt correction action in gui during scan --- src/qudi/gui/scanning/scannergui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index af2dbe9bd..9a90d573a 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -933,6 +933,8 @@ def _toggle_enable_actions(self, enable, exclude_action=None): self._mw.action_history_forward.setEnabled(enable) if exclude_action is not self._mw.action_optimize_position: self._mw.action_optimize_position.setEnabled(enable) + if exclude_action is not self._mw.action_toggle_tilt_correction: + self._mw.action_toggle_tilt_correction.setEnabled(enable) def __get_marker_update_func(self, axes: Union[Tuple[str], Tuple[str, str]]): def update_func(pos: Union[float, Tuple[float, float]]): From 8a758333fa218f6df10363dde8822cf9b7a49455 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 1 Sep 2023 15:29:18 +0200 Subject: [PATCH 58/81] code cleanup --- src/qudi/logic/scanning_probe_logic.py | 33 +++----------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 960c9040b..fd7ce857b 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -340,16 +340,12 @@ def toggle_scan(self, start, scan_axes, caller_id=None): return self.start_scan(scan_axes, caller_id) return self.stop_scan() - def toggle_tilt_correction(self, enable=True, debug_func=False): + def toggle_tilt_correction(self, enable=True): target_pos = self._scanner().get_target() is_enabled = self._scanner().coordinate_transform_enabled - if debug_func: - func = self.__func_debug_transform() - self.log.info("Set test functions for coord transform") - else: - func = self.__transform_func if self._tilt_corr_transform else None + func = self.__transform_func if self._tilt_corr_transform else None if enable: self._scanner().set_coordinate_transform(func, self._tilt_corr_transform) @@ -391,6 +387,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec + # shift coord system to origin, rotate and shift shift back according to LT(x) = (R+s)*x - R*s lin_transform = LinearTransformation3D() shift_vec_transform = LinearTransformation3D() @@ -438,30 +435,6 @@ def tilt_vector_dict_2_array(self, vector, reduced_dim=False): return vecs_arr[0] return vecs_arr - def __func_debug_transform(self): - def transform_to(coord, inverse=False): - # this is a stub function - if inverse: - return {key: 0.5 * val for key, val in coord.items()} - else: - return {key: 2 * val for key, val in coord.items()} - - def transform_to(coord, inverse=False): - - ax_2_idx = lambda ch: ord(ch) - 120 # x->0, y->1, z->2; todo only for these axes - transform = LinearTransformation3D() - - transform.rotate(0, 0, np.pi/10) - # todo: LinearTransformation expects vectors as row (not column) vectors - coord_vec = np.asarray(list(coord.values())).T - coord_transf = transform(coord_vec, invert=inverse).T - # make dict again after vector rotation - coord_transf = {ax: coord_transf[ax_2_idx(ax)] for (ax, val) in coord.items()} - - return coord_transf - - return transform_to - def _update_scan_settings(self, scan_axes, settings): for ax_index, ax in enumerate(scan_axes): # Update scan ranges if needed From 4a12905dc35dc8449522988482b50ac4f97a344a Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 1 Sep 2023 16:07:13 +0200 Subject: [PATCH 59/81] code simplification. Eg., instead of _get_position() for working in bare scanner coordinate, use self.bare_scanner.get_position() --- .../interfuse/ni_scanning_probe_interfuse.py | 99 ++++++++----------- 1 file changed, 41 insertions(+), 58 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 0b51f4ab8..c37638dba 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -122,6 +122,9 @@ def __init__(self, *args, **kwargs): self._thread_lock_cursor = Mutex() self._thread_lock_data = Mutex() + # handle to the uncorrected scanner instance, not wrapped by a potential CoordinateTransformMixin + self.bare_scanner = ScanningProbeDummy + def on_activate(self): # Sanity checks for ni_ao and ni finite sampling io @@ -162,7 +165,7 @@ def on_activate(self): has_position_feedback=False, # TODO incorporate in scanning_probe toolchain square_px_only=False) # - self._target_pos = self._get_position() # get voltages/pos from ni_ao + self._target_pos = self.bare_scanner.get_position(self) # get voltages/pos from ni_ao self._toggle_ao_setpoint_channels(False) # And free ao resources after that self._t_last_move = time.perf_counter() self.__init_ao_timer() @@ -303,7 +306,27 @@ def move_absolute(self, position, velocity=None, blocking=False): Log error and return current target position if something fails or a scan is in progress. """ - return self._move_absolute(position, velocity, blocking) + # assert not self.is_running, 'Cannot move the scanner while, scan is running' + if self.is_scan_running: + self.log.error('Cannot move the scanner while, scan is running') + return self.bare_scanner.get_target(self) + + if not set(position).issubset(self.get_constraints().axes): + self.log.error('Invalid axes name in position') + return self.bare_scanner.get_target(self) + + try: + self._prepare_movement(position, velocity=velocity) + + self.__start_ao_write_timer() + if blocking: + self.__wait_on_move_done() + + self._t_last_move = time.perf_counter() + + return self.bare_scanner.get_target(self) + except: + self.log.exception("Couldn't move: ") def __wait_on_move_done(self): try: @@ -324,7 +347,7 @@ def move_relative(self, distance, velocity=None, blocking=False): Log error and return current target position if something fails or a 1D/2D scan is in progress. """ - current_position = self._get_position() + current_position = self.bare_scanner.get_position(self) end_pos = {ax: current_position[ax] + distance[ax] for ax in distance} self.move_absolute(end_pos, velocity=velocity, blocking=blocking) @@ -336,7 +359,10 @@ def get_target(self): @return dict: current target position per axis. """ - return self._get_target() + if self.is_scan_running: + return self._stored_target_pos + else: + return self._target_pos def get_position(self): """ Get a snapshot of the actual scanner position (i.e. from position feedback sensors). @@ -347,7 +373,13 @@ def get_position(self): @return dict: current position per axis. """ - return self._get_position() + with self._thread_lock_cursor: + if not self._ao_setpoint_channels_active: + self._toggle_ao_setpoint_channels(True) + + pos = self._voltage_dict_to_position_dict(self._ni_ao().setpoints) + + return pos def start_scan(self): try: @@ -383,7 +415,7 @@ def _start_scan(self): with self._thread_lock_data: self._scan_data.new_scan() #self.log.debug(f"New scan data: {self._scan_data.data}, position {self._scan_data._position_data}") - self._stored_target_pos = self._get_target().copy() + self._stored_target_pos = self.bare_scanner.get_target(self) .copy() self.log.debug(f"Target pos at scan start: {self._stored_target_pos}") self._scan_data.scanner_target_at_start = self._stored_target_pos @@ -432,7 +464,7 @@ def _stop_scan(self): # self.log.debug("Module unlocked") self.log.debug(f"Finished scan, move to stored target: {self._stored_target_pos}") - self._move_absolute(self._stored_target_pos) + self.bare_scanner.move_absolute(self, self._stored_target_pos) self._stored_target_pos = dict() def get_scan_data(self): @@ -689,7 +721,7 @@ def __ao_cursor_write_loop(self): t_start = time.perf_counter() try: - current_pos_vec = self._pos_dict_to_vec(self._get_position()) + current_pos_vec = self._pos_dict_to_vec(self.bare_scanner.get_position(self)) with self._thread_lock_cursor: stop_loop = self._abort_cursor_move @@ -760,7 +792,7 @@ def _abort_cursor_movement(self): """ #self.log.debug(f"Aborting move.") - self._target_pos = self._get_position() + self._target_pos = self.bare_scanner.get_position(self) with self._thread_lock_cursor: @@ -819,55 +851,6 @@ def _prepare_movement(self, position, velocity=None): #self.log.debug("Movement prepared") # TODO Keep other axis constant? - def _get_position(self): - """ - Raw position of scanner hardware. Interface method .get_position() might - get wrapped by tilt correction and transform to a virtual coord system. - """ - with self._thread_lock_cursor: - if not self._ao_setpoint_channels_active: - self._toggle_ao_setpoint_channels(True) - - pos = self._voltage_dict_to_position_dict(self._ni_ao().setpoints) - return pos - - def _get_target(self): - """ Raw target position of the scanner hardware. Interface method .get_target() might - get wrapped by tilt correction and transform to a virtual coord system. - - """ - if self.is_scan_running: - return self._stored_target_pos - else: - return self._target_pos - - def _move_absolute(self, position, velocity=None, blocking=False): - """ Move to raw target position of the scanner hardware. Interface method .move absolute() might - get wrapped by tilt correction and transform to a virtual coord system. - - """ - # assert not self.is_running, 'Cannot move the scanner while, scan is running' - if self.is_scan_running: - self.log.error('Cannot move the scanner while, scan is running') - return self._get_target() - - if not set(position).issubset(self.get_constraints().axes): - self.log.error('Invalid axes name in position') - return self._get_target() - - try: - self._prepare_movement(position, velocity=velocity) - - self.__start_ao_write_timer() - if blocking: - self.__wait_on_move_done() - - self._t_last_move = time.perf_counter() - - return self._get_target() - except: - self.log.exception("Couldn't move: ") - def __init_ao_timer(self): self.__ni_ao_write_timer = QtCore.QTimer(parent=self) From 8bb4ee43a048a862bc9b784bc1015b057774b1a5 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 1 Sep 2023 16:28:33 +0200 Subject: [PATCH 60/81] rename functions, add documentation --- src/qudi/logic/scanning_probe_logic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index fd7ce857b..2e9bac754 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -32,7 +32,7 @@ from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar from qudi.util.basis_transformations.basis_transformation \ - import compute_rotation_mat_rodriguez, compute_reduced_vectors, det_changing_axes + import compute_rotation_matrix_to_plane, compute_reduced_vectors, find_changing_axes class ScanningProbeLogic(LogicBase): """ @@ -377,14 +377,14 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): red_support_vecs = red_vecs[:-1,:] shift_vec = red_vecs[-1,:] - tilt_axes = det_changing_axes(support_vecs) + tilt_axes = find_changing_axes(support_vecs) if red_support_vecs.shape != (3,3) or shift_vec.shape[0] != 3: n_dim = support_vecs.shape[1] raise ValueError(f"Can't calculate tilt in >3 dimensions. " f"Given support vectors (dim= {n_dim}) must be constant in exactly {n_dim-3} dims. ") - rot_mat = compute_rotation_mat_rodriguez(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) + rot_mat = compute_rotation_matrix_to_plane(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) shift = shift_vec # shift coord system to origin, rotate and shift shift back according to LT(x) = (R+s)*x - R*s From e8cb6d45b6039184777293e129b55b89bfbf7069 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 4 Sep 2023 08:59:02 +0200 Subject: [PATCH 61/81] fix typo, get class loadable again --- src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index c37638dba..48b19baf0 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -123,7 +123,7 @@ def __init__(self, *args, **kwargs): self._thread_lock_data = Mutex() # handle to the uncorrected scanner instance, not wrapped by a potential CoordinateTransformMixin - self.bare_scanner = ScanningProbeDummy + self.bare_scanner = NiScanningProbeInterfuse def on_activate(self): From 41896ef1c900a83426d89c19567117b25ceb56b3 Mon Sep 17 00:00:00 2001 From: geegee Date: Thu, 7 Sep 2023 12:14:29 +0200 Subject: [PATCH 62/81] Adjusting imports after functions moved in the core. --- src/qudi/logic/scanning_probe_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 2e9bac754..ff5b5a925 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -31,7 +31,7 @@ from qudi.core.connector import Connector from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar -from qudi.util.basis_transformations.basis_transformation \ +from qudi.util.linear_transform \ import compute_rotation_matrix_to_plane, compute_reduced_vectors, find_changing_axes class ScanningProbeLogic(LogicBase): From 2cc154465d5e684c719dbbdc689ce42c9124953f Mon Sep 17 00:00:00 2001 From: Roberto Sailer Date: Mon, 18 Sep 2023 14:14:55 +0200 Subject: [PATCH 63/81] Add more meta-info in saved data via get_scan_data() --- src/qudi/interface/scanning_probe_interface.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 02ad7e897..9fc16273d 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -674,19 +674,25 @@ def get_target(self): def get_position(self): return self.coordinate_transform(super().get_position(), inverse=True) + def get_matr_2_tiltangle(self): + "Calculates the tilt angle of a given rotation matrix. Formula from the website https://en.wikipedia.org/wiki/Rotation_matrix" + rotation_matrix = self._coordinate_transform_matrix.matrix[0:3,0:3] + trace = np.trace(rotation_matrix) + tilt_angle_abs = np.arccos((trace-1)/2) + return tilt_angle_abs + def get_scan_data(self): scan_data = super().get_scan_data() if scan_data: - # todo: transform_func not needed, replace with valuable info (matrix, angle, ..) - tilt_info = {'enabled': self.coordinate_transform_enabled, - } + tilt_info = {'enabled': self.coordinate_transform_enabled} if self.coordinate_transform_enabled: - transform_info = {'transform_func': self._coordinate_transform, - 'tansform_matrix': self._coordinate_transform_matrix.matrix} + transform_info = {'transform_matrix': self._coordinate_transform_matrix.matrix, + 'tilt_angle': self.get_matr_2_tiltangle(), + 'translation': self._coordinate_transform_matrix.matrix[0:3,-1]} tilt_info.update(transform_info) scan_data.coord_transform_info = tilt_info - self.log.debug(f"Get scan data for titl corrected hw called. Info: {tilt_info}") + self.log.info(f"Get scan data for titl corrected hw called. Info: {tilt_info}") return scan_data \ No newline at end of file From 5cbf968e5eec0b23650b0db66ebe5da113f07d2a Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 18 Sep 2023 14:20:10 +0200 Subject: [PATCH 64/81] re-format comments --- src/qudi/interface/scanning_probe_interface.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 9fc16273d..24df20069 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -675,7 +675,11 @@ def get_position(self): return self.coordinate_transform(super().get_position(), inverse=True) def get_matr_2_tiltangle(self): - "Calculates the tilt angle of a given rotation matrix. Formula from the website https://en.wikipedia.org/wiki/Rotation_matrix" + """ + Calculates the tilt angle of a given rotation matrix. + Formula from https://en.wikipedia.org/wiki/Rotation_matrix + """ + rotation_matrix = self._coordinate_transform_matrix.matrix[0:3,0:3] trace = np.trace(rotation_matrix) tilt_angle_abs = np.arccos((trace-1)/2) @@ -693,6 +697,6 @@ def get_scan_data(self): tilt_info.update(transform_info) scan_data.coord_transform_info = tilt_info - self.log.info(f"Get scan data for titl corrected hw called. Info: {tilt_info}") + #self.log.info(f"Get scan data for titl corrected hw called. Info: {tilt_info}") return scan_data \ No newline at end of file From 6233d73aa19ed3c1e0e9608ec066d75ceaf6bfd8 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 21 Sep 2023 14:32:51 +0200 Subject: [PATCH 65/81] allow saving of ScanData.coord_transform_info to serialized StatusVar --- src/qudi/interface/scanning_probe_interface.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/qudi/interface/scanning_probe_interface.py b/src/qudi/interface/scanning_probe_interface.py index 24df20069..46d8bad8e 100644 --- a/src/qudi/interface/scanning_probe_interface.py +++ b/src/qudi/interface/scanning_probe_interface.py @@ -380,7 +380,8 @@ def to_dict(self): 'timestamp': None if self._timestamp is None else self._timestamp.timestamp(), 'data': None if self._data is None else {ch: d.copy() for ch, d in self._data.items()}, 'position_data': None if self._position_data is None else {ax: d.copy() for ax, d in - self._position_data.items()} + self._position_data.items()}, + 'coord_transform_info': self.coord_transform_info } return dict_repr @@ -404,6 +405,9 @@ def from_dict(cls, dict_repr): new_inst._position_data = dict_repr['position_data'] if dict_repr['timestamp'] is not None: new_inst._timestamp = datetime.datetime.fromtimestamp(dict_repr['timestamp']) + if dict_repr['coord_transform_info'] is not None: + new_inst.coord_transform_info = dict_repr['coord_transform_info'] + return new_inst @property @@ -674,9 +678,9 @@ def get_target(self): def get_position(self): return self.coordinate_transform(super().get_position(), inverse=True) - def get_matr_2_tiltangle(self): + def _calc_matr_2_tiltangle(self): """ - Calculates the tilt angle of a given rotation matrix. + Calculates the tilt angle in radians of a given rotation matrix. Formula from https://en.wikipedia.org/wiki/Rotation_matrix """ @@ -691,12 +695,12 @@ def get_scan_data(self): if scan_data: tilt_info = {'enabled': self.coordinate_transform_enabled} if self.coordinate_transform_enabled: + rad_2_deg = lambda phi: phi/np.pi * 180 transform_info = {'transform_matrix': self._coordinate_transform_matrix.matrix, - 'tilt_angle': self.get_matr_2_tiltangle(), + 'tilt_angle (deg)': rad_2_deg(self._calc_matr_2_tiltangle()), 'translation': self._coordinate_transform_matrix.matrix[0:3,-1]} tilt_info.update(transform_info) scan_data.coord_transform_info = tilt_info - #self.log.info(f"Get scan data for titl corrected hw called. Info: {tilt_info}") return scan_data \ No newline at end of file From df7882d754f7fdec1c2fdef908d5e681c8bf52c7 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 21 Sep 2023 14:33:59 +0200 Subject: [PATCH 66/81] get dummy scanner working again --- src/qudi/hardware/dummy/scanning_probe_dummy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index bf8222305..7c103b438 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -94,6 +94,10 @@ def __init__(self, *args, **kwargs): self.__last_line = -1 self.__update_timer = None + # handle to the uncorrected scanner instance, not wrapped by a potential CoordinateTransformMixin + # that transforms to a tilted, virtual coordinate system. + self.bare_scanner = ScanningProbeDummy + def on_activate(self): """ Initialisation performed during activation of the module. """ @@ -526,7 +530,7 @@ def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): class ScanningProbeDummyCorrected(CoordinateTransformMixin, ScanningProbeDummy): def _init_scan_grid(self, x_values, y_values): - # todo: this is fake transformation, as only 2 coordinates of the scan_grid are taken + # this is fake transformation, as only 2 coordinates of the scan_grid are taken vectors = {'x': x_values, 'y': y_values} vectors = self._expand_coordinate(vectors) @@ -534,8 +538,8 @@ def _init_scan_grid(self, x_values, y_values): grid = np.meshgrid(vectors_tilted['x'], vectors_tilted['y'], indexing='ij') - if self.coordinate_transform_enabled: - self.log.debug(f"Transforming scan grid: {grid}") + #if self.coordinate_transform_enabled: + # self.log.debug(f"Transforming scan grid: {grid}") return grid From 669e99e6746911b99cd43e08c4258ee0defa3f36 Mon Sep 17 00:00:00 2001 From: timoML Date: Mon, 27 Nov 2023 15:31:44 +0100 Subject: [PATCH 67/81] Make tilt corrected classes the default ones --- src/qudi/hardware/dummy/scanning_probe_dummy.py | 4 ++-- src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 7c103b438..19d07f1fd 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -29,7 +29,7 @@ from qudi.interface.scanning_probe_interface import ScanConstraints, ScannerAxis, ScannerChannel -class ScanningProbeDummy(ScanningProbeInterface): +class ScanningProbeDummyBare(ScanningProbeInterface): """ Dummy scanning probe microscope. Produces a picture with several gaussian spots. @@ -527,7 +527,7 @@ def _gaussian_2d(xy, amp, pos, sigma, theta=0, offset=0): -(a * x_prime ** 2 + 2 * b * x_prime * y_prime + c * y_prime ** 2)) -class ScanningProbeDummyCorrected(CoordinateTransformMixin, ScanningProbeDummy): +class ScanningProbeDummy(CoordinateTransformMixin, ScanningProbeDummyBare): def _init_scan_grid(self, x_values, y_values): # this is fake transformation, as only 2 coordinates of the scan_grid are taken diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 48b19baf0..e24b3b70f 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -37,7 +37,7 @@ -class NiScanningProbeInterfuse(ScanningProbeInterface): +class NiScanningProbeInterfuseBare(ScanningProbeInterface): """ This interfuse combines modules of a National Instrument device to make up a scanning probe hardware. One module for software timed analog output (NIXSeriesAnalogOutput) to position e.g. a scanner to a specific @@ -929,7 +929,7 @@ def is_full(self): return self.number_of_non_nan_values == self.frame_size -class NiScanningProbeInterfuseCorrected(CoordinateTransformMixin, NiScanningProbeInterfuse): +class NiScanningProbeInterfuse(CoordinateTransformMixin, NiScanningProbeInterfuseBare): def _initialize_ni_scan_arrays(self, scan_data): scan_coords = self._get_scan_lines(scan_data) From 8fa732812d3b1ba0fe26528be7b0b35fbc83fc28 Mon Sep 17 00:00:00 2001 From: timoML Date: Fri, 8 Dec 2023 16:59:10 +0100 Subject: [PATCH 68/81] fix broken new, tilt-corrected default classes --- src/qudi/hardware/dummy/scanning_probe_dummy.py | 2 +- src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qudi/hardware/dummy/scanning_probe_dummy.py b/src/qudi/hardware/dummy/scanning_probe_dummy.py index 19d07f1fd..4197f6850 100644 --- a/src/qudi/hardware/dummy/scanning_probe_dummy.py +++ b/src/qudi/hardware/dummy/scanning_probe_dummy.py @@ -96,7 +96,7 @@ def __init__(self, *args, **kwargs): # handle to the uncorrected scanner instance, not wrapped by a potential CoordinateTransformMixin # that transforms to a tilted, virtual coordinate system. - self.bare_scanner = ScanningProbeDummy + self.bare_scanner = ScanningProbeDummyBare def on_activate(self): """ Initialisation performed during activation of the module. diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index e24b3b70f..fce8723b2 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -46,7 +46,7 @@ class NiScanningProbeInterfuseBare(ScanningProbeInterface): Example config for copy-paste: ni_scanning_probe: - module.Class: 'interfuse.ni_scanning_probe_interfuse.NiScanningProbeInterfuse' + module.Class: 'interfuse.ni_scanning_probe_interfuse.NiScanningProbeInterfuseBare' connect: scan_hardware: 'ni_finite_sampling_io' analog_output: 'ni_ao' @@ -123,7 +123,7 @@ def __init__(self, *args, **kwargs): self._thread_lock_data = Mutex() # handle to the uncorrected scanner instance, not wrapped by a potential CoordinateTransformMixin - self.bare_scanner = NiScanningProbeInterfuse + self.bare_scanner = NiScanningProbeInterfuseBare def on_activate(self): From 008ac9b67433a086e4fc158033e1c978a3802a70 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 11 Dec 2023 10:35:19 +0100 Subject: [PATCH 69/81] default to empty history if loading from StatusVar fails (eg. because history without tilt correction) --- src/qudi/logic/scanning_data_logic.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/qudi/logic/scanning_data_logic.py b/src/qudi/logic/scanning_data_logic.py index fd5c3a4c4..82fe40088 100644 --- a/src/qudi/logic/scanning_data_logic.py +++ b/src/qudi/logic/scanning_data_logic.py @@ -107,7 +107,13 @@ def __scan_history_to_dicts(self, history): @_scan_history.constructor def __scan_history_from_dicts(self, history_dicts): - return [ScanData.from_dict(hist_dict) for hist_dict in history_dicts] + try: + history = [ScanData.from_dict(hist_dict) for hist_dict in history_dicts] + except Exception as e: + self.log.warning(f"Couldn't restore scan history from StatusVar. Scan history will be empty: {repr(e)}") + history = [] + + return history def get_current_scan_data(self, scan_axes=None): """ From 283ccf6d5b1b596469449196c97834dd2edbe588 Mon Sep 17 00:00:00 2001 From: timoML Date: Mon, 11 Dec 2023 22:41:57 +0100 Subject: [PATCH 70/81] update gui on changed tilt correcton support vectors in logic --- src/qudi/gui/scanning/scannergui.py | 35 +++++++++++++++++++++----- src/qudi/logic/scanning_probe_logic.py | 12 ++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 699d32585..c80a67599 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -260,12 +260,14 @@ def on_activate(self): QtCore.Qt.QueuedConnection) tilt_widget.tilt_set_04_pushButton.clicked.connect(lambda: self.tilt_corr_set_support_vector(3), QtCore.Qt.QueuedConnection) - tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.tilt_corr_support_vector_updated, + tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.connect(self.apply_tilt_corr_support_vectors, QtCore.Qt.QueuedConnection) self._mw.action_toggle_tilt_correction.triggered.connect(self.toggle_tilt_correction, QtCore.Qt.QueuedConnection) - [box.valueChanged.connect(self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) + [box.valueChanged.connect(self.apply_tilt_corr_support_vectors, QtCore.Qt.QueuedConnection) for box_row in tilt_widget.support_vecs_box for box in box_row] + self._scanning_logic().sigTiltCorrSettingsChanged.connect( + self.tilt_corr_support_vector_updated, QtCore.Qt.QueuedConnection) # Initialize dockwidgets to default view self.restore_default_view() @@ -318,6 +320,7 @@ def on_deactivate(self): tilt_widget.tilt_set_03_pushButton.clicked.disconnect() tilt_widget.tilt_set_04_pushButton.clicked.disconnect() tilt_widget.auto_origin_switch.toggle_switch.sigStateChanged.disconnect() + self._scanning_logic().sigTiltCorrSettingsChanged.disconnect() self._mw.action_toggle_tilt_correction.triggered.disconnect() def show(self): @@ -528,7 +531,7 @@ def _restore_tilt_correction(self): self.tilt_correction_dockwidget.set_support_vector(vector, idx) except (ValueError, KeyError): pass - self.tilt_corr_support_vector_updated() + self.apply_tilt_corr_support_vectors() @QtCore.Slot(tuple) def save_scan_data(self, scan_axes=None): @@ -1063,9 +1066,28 @@ def tilt_corr_set_support_vector(self, idx_vector=0): target = self._scanning_logic().scanner_target self.tilt_correction_dockwidget.set_support_vector(target, idx_vector) - self.tilt_corr_support_vector_updated() + self.apply_tilt_corr_support_vectors() - def tilt_corr_support_vector_updated(self): + def tilt_corr_support_vector_updated(self, sup_vecs=None, shift_vec=None, caller_id=None): + """ + Signal new vectors from logic and update gui accordingly. + :param sup_vecs: + :param shift_vec: + :return: + """ + + #self.log.debug(f"Update vectors from logic: {sup_vecs}, {shift_vec}") + + tilt_widget = self.tilt_correction_dockwidget + + for i_row, box_row in enumerate(tilt_widget.support_vecs_box): + for j_col, box in enumerate(box_row): + if i_row == len(tilt_widget.support_vecs_box)-1: + box.setValue(shift_vec[j_col]) + else: + box.setValue(sup_vecs[i_row, j_col]) + + def apply_tilt_corr_support_vectors(self): support_vecs = self.tilt_correction_dockwidget.support_vecs_box support_vecs_val = self.tilt_correction_dockwidget.support_vectors @@ -1089,7 +1111,8 @@ def tilt_corr_support_vector_updated(self): shift_vec_arr = None support_vecs_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[:-1]) self._scanning_logic().configure_tilt_correction(support_vecs_arr, - shift_vec_arr) + shift_vec_arr, + caller_id=self.module_uuid) self._mw.action_toggle_tilt_correction.setEnabled(True) def toggle_tilt_correction(self, state): diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index ff5b5a925..5228bf162 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -68,6 +68,7 @@ class ScanningProbeLogic(LogicBase): sigScanStateChanged = QtCore.Signal(bool, object, object) sigScannerTargetChanged = QtCore.Signal(dict, object) sigScanSettingsChanged = QtCore.Signal(dict) + sigTiltCorrSettingsChanged = QtCore.Signal(np.ndarray, np.ndarray, object) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -356,12 +357,16 @@ def toggle_tilt_correction(self, enable=True): # set target pos again with updated, (dis-) engaged tilt correction self.set_target_position(target_pos, move_blocking=True) - def configure_tilt_correction(self, support_vecs=None, shift_vec=None): + def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id=None): if support_vecs is None: self._tilt_corr_transform = None return + if caller_id is None: + # if gui is calling, don't emit back update + caller_id = self._curr_caller_id + support_vecs = np.asarray(support_vecs) if support_vecs.shape[0] != 3: @@ -404,6 +409,11 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None): self._tilt_corr_transform = lin_transform self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] + #self.log.debug(f"Configured tilt corr: {support_vecs}, {shift_vec}") + + if caller_id == self._curr_caller_id: + self.sigTiltCorrSettingsChanged.emit(support_vecs, shift_vec, self._curr_caller_id) + def tilt_vector_dict_2_array(self, vector, reduced_dim=False): """ Convert vectors given as dict (with axes keys) to arrays and ensure correct order. From 72c287fac48977a41031a54d74dd3d9fdca92b06 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 12 Dec 2023 12:04:37 +0100 Subject: [PATCH 71/81] rework tilt correction signals - keep tilt correction settings as StatusVar in logic, not gui - configure tilt correction with support vector list of dicts (incl axes) --- src/qudi/gui/scanning/scannergui.py | 57 +++++++++++-------- .../scanning/tilt_correction_dockwidget.py | 4 ++ src/qudi/logic/scanning_probe_logic.py | 55 +++++++++++++----- 3 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index c80a67599..ac09304cf 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -123,7 +123,6 @@ class ScannerGui(GuiBase): # status vars _window_state = StatusVar(name='window_state', default=None) _window_geometry = StatusVar(name='window_geometry', default=None) - _tilt_correction_vectors = StatusVar(name='tilt_correction_vectors', default=[]) # signals sigScannerTargetChanged = QtCore.Signal(dict, object) @@ -525,12 +524,10 @@ def restore_default_view(self): return def _restore_tilt_correction(self): - if self._tilt_correction_vectors: - for idx, vector in enumerate(self._tilt_correction_vectors): - try: - self.tilt_correction_dockwidget.set_support_vector(vector, idx) - except (ValueError, KeyError): - pass + + tilt_settings = self._scanning_logic().tilt_correction_settings + + self.tilt_corr_support_vector_updated(tilt_settings) self.apply_tilt_corr_support_vectors() @QtCore.Slot(tuple) @@ -1068,7 +1065,7 @@ def tilt_corr_set_support_vector(self, idx_vector=0): self.tilt_correction_dockwidget.set_support_vector(target, idx_vector) self.apply_tilt_corr_support_vectors() - def tilt_corr_support_vector_updated(self, sup_vecs=None, shift_vec=None, caller_id=None): + def tilt_corr_support_vector_updated(self, settings=None): """ Signal new vectors from logic and update gui accordingly. :param sup_vecs: @@ -1076,16 +1073,28 @@ def tilt_corr_support_vector_updated(self, sup_vecs=None, shift_vec=None, caller :return: """ - #self.log.debug(f"Update vectors from logic: {sup_vecs}, {shift_vec}") + if settings: + sup_vecs = np.asarray([settings['vec_1'], settings['vec_2'], settings['vec_3']]) + shift_vec = settings['vec_shift'] + auto_origin = settings['auto_origin'] + #self.log.debug(f"Update vectors from logic: {sup_vecs}, {shift_vec}") - tilt_widget = self.tilt_correction_dockwidget + if shift_vec is None and auto_origin: + shift_vec = {ax: np.inf for ax in sup_vecs[0].keys()} - for i_row, box_row in enumerate(tilt_widget.support_vecs_box): - for j_col, box in enumerate(box_row): - if i_row == len(tilt_widget.support_vecs_box)-1: - box.setValue(shift_vec[j_col]) - else: - box.setValue(sup_vecs[i_row, j_col]) + tilt_widget = self.tilt_correction_dockwidget + + auto_state = 'ON' if auto_origin else 'OFF' + tilt_widget.set_auto_origin(auto_state) + + for i_row, box_row in enumerate(tilt_widget.support_vecs_box): + for j_col, box in enumerate(box_row): + if i_row == len(tilt_widget.support_vecs_box)-1: + vec_arr = self._scanning_logic().tilt_vector_dict_2_array(shift_vec) + else: + vec_arr = self._scanning_logic().tilt_vector_dict_2_array(sup_vecs[i_row]) + + box.setValue(vec_arr[j_col]) def apply_tilt_corr_support_vectors(self): @@ -1096,7 +1105,8 @@ def apply_tilt_corr_support_vectors(self): dim_idxs = [(idx, key) for idx, key in enumerate(self._scanning_logic().scanner_axes.keys())] all_vecs_valid = True - for vec in [0,1,2,3]: + vecs_to_check = [0,1,2] if self.tilt_correction_dockwidget.auto_origin else [0,1,2,3] + for vec in vecs_to_check: vecs_valid = [support_vecs[vec][dim[0]].is_valid for dim in dim_idxs] all_vecs_valid = np.all(vecs_valid) and all_vecs_valid @@ -1106,12 +1116,13 @@ def apply_tilt_corr_support_vectors(self): self._mw.action_toggle_tilt_correction.setEnabled(False) if all_vecs_valid: - shift_vec_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[-1]) - if not np.all([np.isfinite(el) for el in shift_vec_arr]): - shift_vec_arr = None - support_vecs_arr = self._scanning_logic().tilt_vector_dict_2_array(support_vecs_val[:-1]) - self._scanning_logic().configure_tilt_correction(support_vecs_arr, - shift_vec_arr, + shift_vec =support_vecs_val[-1] + if self.tilt_correction_dockwidget.auto_origin: + shift_vec = None + + support_vecs = support_vecs_val[:-1] + self._scanning_logic().configure_tilt_correction(support_vecs, + shift_vec, caller_id=self.module_uuid) self._mw.action_toggle_tilt_correction.setEnabled(True) diff --git a/src/qudi/gui/scanning/tilt_correction_dockwidget.py b/src/qudi/gui/scanning/tilt_correction_dockwidget.py index f702a977b..56202b675 100644 --- a/src/qudi/gui/scanning/tilt_correction_dockwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dockwidget.py @@ -121,6 +121,10 @@ def set_support_vector(self, vector, idx): def auto_origin(self): return True if self.auto_origin_switch.switch_state == 'ON' else False + def set_auto_origin(self, state): + self.auto_origin_switch.switch_state = state + self.auto_origin_changed(state) + def auto_origin_changed(self, state): auto_enabled = True if state == 'ON' else False diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 5228bf162..cc5e7ef16 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -60,6 +60,7 @@ class ScanningProbeLogic(LogicBase): _scan_ranges = StatusVar(name='scan_ranges', default=None) _scan_resolution = StatusVar(name='scan_resolution', default=None) _scan_frequency = StatusVar(name='scan_frequency', default=None) + _tilt_corr_settings = StatusVar(name='tilt_corr_settings', default={}) # config options _min_poll_interval = ConfigOption(name='min_poll_interval', default=None) @@ -68,7 +69,7 @@ class ScanningProbeLogic(LogicBase): sigScanStateChanged = QtCore.Signal(bool, object, object) sigScannerTargetChanged = QtCore.Signal(dict, object) sigScanSettingsChanged = QtCore.Signal(dict) - sigTiltCorrSettingsChanged = QtCore.Signal(np.ndarray, np.ndarray, object) + sigTiltCorrSettingsChanged = QtCore.Signal(dict, object) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -81,6 +82,11 @@ def __init__(self, *args, **kwargs): self.__scan_stop_requested = True self._curr_caller_id = self.module_uuid self._tilt_corr_transform = None + self._tilt_corr_settings = {'auto_origin': None, + 'vec_1': None, + 'vec_2': None, + 'vec_3': None, + 'vec_shift': None} def on_activate(self): """ Initialisation performed during activation of the module. @@ -357,7 +363,19 @@ def toggle_tilt_correction(self, enable=True): # set target pos again with updated, (dis-) engaged tilt correction self.set_target_position(target_pos, move_blocking=True) + @property + def tilt_correction_settings(self): + return self._tilt_corr_settings + def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id=None): + """ + Confiugre the titl correction with a set of support vector that define the tilted plane + that should be horizontal after the correction + + @param list support_vecs: list of dicts. Each dict contains the scan axis as keys. + @param dict shift_vec: Vector that defines the origin of rotation + @param int caller_id:. Qudi module object that is calling. + """ if support_vecs is None: self._tilt_corr_transform = None @@ -367,30 +385,33 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id # if gui is calling, don't emit back update caller_id = self._curr_caller_id - support_vecs = np.asarray(support_vecs) + support_vecs_arr = np.asarray(self.tilt_vector_dict_2_array(support_vecs, reduced_dim=False)) + if shift_vec is not None: + shift_vec_arr = np.array(self.tilt_vector_dict_2_array(shift_vec, reduced_dim=False)) - if support_vecs.shape[0] != 3: - raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs.shape[0]}") + if support_vecs_arr.shape[0] != 3: + raise ValueError(f"Need 3 n-dim support vectors, not {support_vecs_arr.shape[0]}") + auto_origin = False if shift_vec is None: - red_support_vecs = compute_reduced_vectors(support_vecs) - shift_vec = np.mean(red_support_vecs, axis=0) + auto_origin = True + red_support_vecs = compute_reduced_vectors(support_vecs_arr) + shift_vec_arr = np.mean(red_support_vecs, axis=0) else: - shift_vec = np.asarray(shift_vec) - red_support_vecs = np.vstack([support_vecs, shift_vec]) + red_support_vecs = np.vstack([support_vecs_arr, shift_vec_arr]) red_vecs = compute_reduced_vectors(red_support_vecs) red_support_vecs = red_vecs[:-1,:] - shift_vec = red_vecs[-1,:] + shift_vec_arr = red_vecs[-1,:] - tilt_axes = find_changing_axes(support_vecs) + tilt_axes = find_changing_axes(support_vecs_arr) - if red_support_vecs.shape != (3,3) or shift_vec.shape[0] != 3: - n_dim = support_vecs.shape[1] + if red_support_vecs.shape != (3,3) or shift_vec_arr.shape[0] != 3: + n_dim = support_vecs_arr.shape[1] raise ValueError(f"Can't calculate tilt in >3 dimensions. " f"Given support vectors (dim= {n_dim}) must be constant in exactly {n_dim-3} dims. ") rot_mat = compute_rotation_matrix_to_plane(red_support_vecs[0], red_support_vecs[1], red_support_vecs[2]) - shift = shift_vec + shift = shift_vec_arr # shift coord system to origin, rotate and shift shift back according to LT(x) = (R+s)*x - R*s lin_transform = LinearTransformation3D() @@ -408,11 +429,15 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id self._tilt_corr_transform = lin_transform self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] - + self._tilt_corr_settings = {'auto_origin': auto_origin, + 'vec_1': support_vecs[0], + 'vec_2': support_vecs[1], + 'vec_3': support_vecs[2], + 'vec_shift': shift_vec} #self.log.debug(f"Configured tilt corr: {support_vecs}, {shift_vec}") if caller_id == self._curr_caller_id: - self.sigTiltCorrSettingsChanged.emit(support_vecs, shift_vec, self._curr_caller_id) + self.sigTiltCorrSettingsChanged.emit(self._tilt_corr_settings, self._curr_caller_id) def tilt_vector_dict_2_array(self, vector, reduced_dim=False): """ From ce0a0365edc7072ca5ccb91dddd4460e9ca2bd97 Mon Sep 17 00:00:00 2001 From: timoML Date: Mon, 18 Dec 2023 09:01:55 +0100 Subject: [PATCH 72/81] code cleanup --- src/qudi/gui/scanning/scannergui.py | 14 ++++++-------- .../gui/scanning/tilt_correction_dockwidget.py | 2 +- src/qudi/logic/scanning_probe_logic.py | 12 +++++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index ac09304cf..69a7e816e 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -534,7 +534,7 @@ def _restore_tilt_correction(self): def save_scan_data(self, scan_axes=None): """ Save data for a given (or all) scan axis. - @param tuple: Axis to save. Save all currently displayed if None. + @param tuple scan_axes: Axis to save. Save all currently displayed if None. """ self.sigShowSaveDialog.emit(True) try: @@ -1065,12 +1065,12 @@ def tilt_corr_set_support_vector(self, idx_vector=0): self.tilt_correction_dockwidget.set_support_vector(target, idx_vector) self.apply_tilt_corr_support_vectors() - def tilt_corr_support_vector_updated(self, settings=None): + def tilt_corr_support_vector_updated(self, settings): """ Signal new vectors from logic and update gui accordingly. - :param sup_vecs: - :param shift_vec: - :return: + + @param dict settings: scanning probe logic settings dict + @return: """ if settings: @@ -1100,7 +1100,6 @@ def apply_tilt_corr_support_vectors(self): support_vecs = self.tilt_correction_dockwidget.support_vecs_box support_vecs_val = self.tilt_correction_dockwidget.support_vectors - self._tilt_correction_vectors = support_vecs_val dim_idxs = [(idx, key) for idx, key in enumerate(self._scanning_logic().scanner_axes.keys())] @@ -1112,11 +1111,10 @@ def apply_tilt_corr_support_vectors(self): self.toggle_tilt_correction(False) self._scanning_logic().configure_tilt_correction(None, None) - self._mw.action_toggle_tilt_correction.setEnabled(False) if all_vecs_valid: - shift_vec =support_vecs_val[-1] + shift_vec = support_vecs_val[-1] if self.tilt_correction_dockwidget.auto_origin: shift_vec = None diff --git a/src/qudi/gui/scanning/tilt_correction_dockwidget.py b/src/qudi/gui/scanning/tilt_correction_dockwidget.py index 56202b675..bb1c948f8 100644 --- a/src/qudi/gui/scanning/tilt_correction_dockwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dockwidget.py @@ -131,7 +131,7 @@ def auto_origin_changed(self, state): [el.setEnabled(not auto_enabled) for el in self.support_vecs_box[-1]] - # nan renders the gui boxes invalid/red, so instead inf + # nan renders the gui boxes invalid/red, so if auto=on instead inf [el.setValue(np.inf) for el in self.support_vecs_box[-1]] if not auto_enabled: [el.setValue(np.nan) for el in self.support_vecs_box[-1]] diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index cc5e7ef16..c11c45885 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -82,6 +82,7 @@ def __init__(self, *args, **kwargs): self.__scan_stop_requested = True self._curr_caller_id = self.module_uuid self._tilt_corr_transform = None + self._tilt_corr_axes = [] self._tilt_corr_settings = {'auto_origin': None, 'vec_1': None, 'vec_2': None, @@ -369,7 +370,7 @@ def tilt_correction_settings(self): def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id=None): """ - Confiugre the titl correction with a set of support vector that define the tilted plane + Configure the tilt correction with a set of support vector that define the tilted plane that should be horizontal after the correction @param list support_vecs: list of dicts. Each dict contains the scan axis as keys. @@ -424,8 +425,6 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id shift_back = shift_vec_transform(-shift) lin_transform.translate(shift_back[0], shift_back[1], shift_back[2]) - #self.log.debug(f"Shift vec {shift}, shift back {shift_back}") - #self.log.debug(f"Matrix: {lin_transform.matrix}") self._tilt_corr_transform = lin_transform self._tilt_corr_axes = [el for idx, el in enumerate(self._scan_axes) if tilt_axes[idx]] @@ -434,6 +433,9 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id 'vec_2': support_vecs[1], 'vec_3': support_vecs[2], 'vec_shift': shift_vec} + + #self.log.debug(f"Shift vec {shift}, shift back {shift_back}") + #self.log.debug(f"Matrix: {lin_transform.matrix}") #self.log.debug(f"Configured tilt corr: {support_vecs}, {shift_vec}") if caller_id == self._curr_caller_id: @@ -442,9 +444,9 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id def tilt_vector_dict_2_array(self, vector, reduced_dim=False): """ Convert vectors given as dict (with axes keys) to arrays and ensure correct order. - vector: dict (single coord or arrays per key) or list of dicts - return: np.array or list of np.array + @param dict vector: (single coord or arrays per key) or list of dicts + @return np.array or list of np.array: vector(s) as array """ axes = self._tilt_corr_axes if reduced_dim else self._scan_axes.keys() From 93341d9239ad389bfcba8eebd82fd6fbbcfd22e7 Mon Sep 17 00:00:00 2001 From: timoML Date: Mon, 18 Dec 2023 09:53:17 +0100 Subject: [PATCH 73/81] add access to auto calculated shift vec --- src/qudi/logic/scanning_probe_logic.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index c11c45885..2463af81d 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -398,6 +398,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id auto_origin = True red_support_vecs = compute_reduced_vectors(support_vecs_arr) shift_vec_arr = np.mean(red_support_vecs, axis=0) + shift_vec = self.tilt_vector_array_2_dict(shift_vec_arr, reduced_dim=True) else: red_support_vecs = np.vstack([support_vecs_arr, shift_vec_arr]) red_vecs = compute_reduced_vectors(red_support_vecs) @@ -446,6 +447,7 @@ def tilt_vector_dict_2_array(self, vector, reduced_dim=False): Convert vectors given as dict (with axes keys) to arrays and ensure correct order. @param dict vector: (single coord or arrays per key) or list of dicts + @param bool reduced_dim: The vector given has been reduced to 3 dims (from n-dim for arbitrary vectors) @return np.array or list of np.array: vector(s) as array """ @@ -472,6 +474,11 @@ def tilt_vector_dict_2_array(self, vector, reduced_dim=False): return vecs_arr[0] return vecs_arr + def tilt_vector_array_2_dict(self, array, reduced_dim=True): + axes = self._tilt_corr_axes if reduced_dim else self._scan_axes.keys() + + return {ax: array[idx] for idx, ax in enumerate(axes)} + def _update_scan_settings(self, scan_axes, settings): for ax_index, ax in enumerate(scan_axes): # Update scan ranges if needed From d3efd22b246b9ef9c72db997ebbea23ba59cf6eb Mon Sep 17 00:00:00 2001 From: timoML Date: Mon, 18 Dec 2023 10:27:02 +0100 Subject: [PATCH 74/81] fix potential wrong axes order in update of support vector --- src/qudi/gui/scanning/scannergui.py | 18 ++++++++++-------- src/qudi/logic/scanning_probe_logic.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 69a7e816e..4f7dcc39f 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1073,28 +1073,30 @@ def tilt_corr_support_vector_updated(self, settings): @return: """ + tilt_widget = self.tilt_correction_dockwidget + if settings: sup_vecs = np.asarray([settings['vec_1'], settings['vec_2'], settings['vec_3']]) - shift_vec = settings['vec_shift'] + shift_vec = settings.get('vec_shift', {}) auto_origin = settings['auto_origin'] + axes = list(tilt_widget.support_vectors[0].keys()) #self.log.debug(f"Update vectors from logic: {sup_vecs}, {shift_vec}") - if shift_vec is None and auto_origin: - shift_vec = {ax: np.inf for ax in sup_vecs[0].keys()} - - tilt_widget = self.tilt_correction_dockwidget + default_vec = {ax: np.inf for ax in axes} + shift_vec = {**default_vec, **shift_vec} auto_state = 'ON' if auto_origin else 'OFF' tilt_widget.set_auto_origin(auto_state) for i_row, box_row in enumerate(tilt_widget.support_vecs_box): for j_col, box in enumerate(box_row): + ax = axes[j_col] if i_row == len(tilt_widget.support_vecs_box)-1: - vec_arr = self._scanning_logic().tilt_vector_dict_2_array(shift_vec) + vec = shift_vec else: - vec_arr = self._scanning_logic().tilt_vector_dict_2_array(sup_vecs[i_row]) + vec = sup_vecs[i_row] - box.setValue(vec_arr[j_col]) + box.setValue(vec[ax]) def apply_tilt_corr_support_vectors(self): diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 2463af81d..954e3bcd5 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -374,7 +374,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id that should be horizontal after the correction @param list support_vecs: list of dicts. Each dict contains the scan axis as keys. - @param dict shift_vec: Vector that defines the origin of rotation + @param dict shift_vec: Vector that defines the origin of rotation. @param int caller_id:. Qudi module object that is calling. """ From 3b900f34ed0cb1d7d7a7acdb317bb5cd9a0df3f8 Mon Sep 17 00:00:00 2001 From: timoML Date: Tue, 19 Dec 2023 10:25:37 +0100 Subject: [PATCH 75/81] Improved signals. Break recursion by blocking signals. - Auto calculated shift vector now displayed in gui --- src/qudi/gui/scanning/scannergui.py | 10 +++++++--- .../gui/scanning/tilt_correction_dockwidget.py | 16 +++++++++------- src/qudi/logic/scanning_probe_logic.py | 12 +++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 4f7dcc39f..95a0c58b0 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1086,7 +1086,10 @@ def tilt_corr_support_vector_updated(self, settings): shift_vec = {**default_vec, **shift_vec} auto_state = 'ON' if auto_origin else 'OFF' - tilt_widget.set_auto_origin(auto_state) + + tilt_widget.blockSignals(True) + tilt_widget.set_auto_origin(auto_state, reset=False) + tilt_widget.blockSignals(False) for i_row, box_row in enumerate(tilt_widget.support_vecs_box): for j_col, box in enumerate(box_row): @@ -1096,7 +1099,9 @@ def tilt_corr_support_vector_updated(self, settings): else: vec = sup_vecs[i_row] + box.blockSignals(True) box.setValue(vec[ax]) + box.blockSignals(False) def apply_tilt_corr_support_vectors(self): @@ -1122,8 +1127,7 @@ def apply_tilt_corr_support_vectors(self): support_vecs = support_vecs_val[:-1] self._scanning_logic().configure_tilt_correction(support_vecs, - shift_vec, - caller_id=self.module_uuid) + shift_vec) self._mw.action_toggle_tilt_correction.setEnabled(True) def toggle_tilt_correction(self, state): diff --git a/src/qudi/gui/scanning/tilt_correction_dockwidget.py b/src/qudi/gui/scanning/tilt_correction_dockwidget.py index bb1c948f8..0f79f6088 100644 --- a/src/qudi/gui/scanning/tilt_correction_dockwidget.py +++ b/src/qudi/gui/scanning/tilt_correction_dockwidget.py @@ -121,20 +121,22 @@ def set_support_vector(self, vector, idx): def auto_origin(self): return True if self.auto_origin_switch.switch_state == 'ON' else False - def set_auto_origin(self, state): + def set_auto_origin(self, state, reset=True): self.auto_origin_switch.switch_state = state - self.auto_origin_changed(state) + self.auto_origin_changed(state, reset=reset) - def auto_origin_changed(self, state): + def auto_origin_changed(self, state, reset=True): auto_enabled = True if state == 'ON' else False [el.setEnabled(not auto_enabled) for el in self.support_vecs_box[-1]] - # nan renders the gui boxes invalid/red, so if auto=on instead inf - [el.setValue(np.inf) for el in self.support_vecs_box[-1]] - if not auto_enabled: - [el.setValue(np.nan) for el in self.support_vecs_box[-1]] + if reset: + # from gui, reset values for safety. Users should think whether old values are safe. + # nan renders the gui boxes invalid/red, so if auto=on instead inf + [el.setValue(np.inf) for el in self.support_vecs_box[-1]] + if not auto_enabled: + [el.setValue(np.nan) for el in self.support_vecs_box[-1]] self.tilt_set_04_pushButton.setEnabled(not auto_enabled) diff --git a/src/qudi/logic/scanning_probe_logic.py b/src/qudi/logic/scanning_probe_logic.py index 954e3bcd5..302ced7df 100644 --- a/src/qudi/logic/scanning_probe_logic.py +++ b/src/qudi/logic/scanning_probe_logic.py @@ -69,7 +69,7 @@ class ScanningProbeLogic(LogicBase): sigScanStateChanged = QtCore.Signal(bool, object, object) sigScannerTargetChanged = QtCore.Signal(dict, object) sigScanSettingsChanged = QtCore.Signal(dict) - sigTiltCorrSettingsChanged = QtCore.Signal(dict, object) + sigTiltCorrSettingsChanged = QtCore.Signal(dict) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -368,24 +368,19 @@ def toggle_tilt_correction(self, enable=True): def tilt_correction_settings(self): return self._tilt_corr_settings - def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id=None): + def configure_tilt_correction(self, support_vecs=None, shift_vec=None): """ Configure the tilt correction with a set of support vector that define the tilted plane that should be horizontal after the correction @param list support_vecs: list of dicts. Each dict contains the scan axis as keys. @param dict shift_vec: Vector that defines the origin of rotation. - @param int caller_id:. Qudi module object that is calling. """ if support_vecs is None: self._tilt_corr_transform = None return - if caller_id is None: - # if gui is calling, don't emit back update - caller_id = self._curr_caller_id - support_vecs_arr = np.asarray(self.tilt_vector_dict_2_array(support_vecs, reduced_dim=False)) if shift_vec is not None: shift_vec_arr = np.array(self.tilt_vector_dict_2_array(shift_vec, reduced_dim=False)) @@ -439,8 +434,7 @@ def configure_tilt_correction(self, support_vecs=None, shift_vec=None, caller_id #self.log.debug(f"Matrix: {lin_transform.matrix}") #self.log.debug(f"Configured tilt corr: {support_vecs}, {shift_vec}") - if caller_id == self._curr_caller_id: - self.sigTiltCorrSettingsChanged.emit(self._tilt_corr_settings, self._curr_caller_id) + self.sigTiltCorrSettingsChanged.emit(self._tilt_corr_settings) def tilt_vector_dict_2_array(self, vector, reduced_dim=False): """ From 1f83143a20f4fa97b372e39d8a123f9e0079684b Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 18 Jan 2024 09:38:21 +0100 Subject: [PATCH 76/81] fix restoring settings if shift_vec=None --- src/qudi/gui/scanning/scannergui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qudi/gui/scanning/scannergui.py b/src/qudi/gui/scanning/scannergui.py index 95a0c58b0..586d43bb1 100644 --- a/src/qudi/gui/scanning/scannergui.py +++ b/src/qudi/gui/scanning/scannergui.py @@ -1078,6 +1078,7 @@ def tilt_corr_support_vector_updated(self, settings): if settings: sup_vecs = np.asarray([settings['vec_1'], settings['vec_2'], settings['vec_3']]) shift_vec = settings.get('vec_shift', {}) + shift_vec = {} if shift_vec is None else shift_vec auto_origin = settings['auto_origin'] axes = list(tilt_widget.support_vectors[0].keys()) #self.log.debug(f"Update vectors from logic: {sup_vecs}, {shift_vec}") From 03961c05da08409735a0f8ba10a1e2c3304c6c41 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 23 Jan 2024 10:41:03 +0100 Subject: [PATCH 77/81] abort configuration of out-of-range scans, instead of silently clipping --- .../interfuse/ni_scanning_probe_interfuse.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index fce8723b2..817fc48fe 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -284,7 +284,7 @@ def configure_scan(self, scan_settings): self._ni_finite_sampling_io().set_output_mode(SamplingOutputMode.JUMP_LIST) - ni_scan_dict = self._initialize_ni_scan_arrays(self._scan_data) + ni_scan_dict = self._init_ni_scan_arrays(self._scan_data) self._ni_finite_sampling_io().set_frame_data(ni_scan_dict) @@ -699,7 +699,22 @@ def _get_scan_lines(self, scan_data): return self._expand_coordinate(coord_dict) - def _initialize_ni_scan_arrays(self, scan_data): + def _init_scan_grid(self, scan_data): + scan_coords = self._get_scan_lines(scan_data) + + return scan_coords + + def _check_scan_grid(self, scan_coords): + + for ax, coords in scan_coords.items(): + position_min = self.get_constraints().axes[ax].min_value + position_max = self.get_constraints().axes[ax].max_value + out_of_range = any(coords < position_min) or any(coords > position_max) + + if out_of_range: + raise ValueError(f"Scan axis {ax} out of range [{position_min}, {position_max}]") + + def _init_ni_scan_arrays(self, scan_data): """ @param ScanData scan_data: The desired ScanData instance @@ -712,7 +727,11 @@ def _initialize_ni_scan_arrays(self, scan_data): assert isinstance(scan_data, ScanData), 'This function requires a scan_data object as input' - scan_coords = self._get_scan_lines(scan_data) + scan_coords = self._init_scan_grid(scan_data) + self._check_scan_grid(scan_coords) + + #self.log.debug(f"created scan grid: {scan_coords}") + scan_voltages = {self._ni_channel_mapping[ax]: self._position_to_voltage(ax, val) for ax, val in scan_coords.items()} return scan_voltages @@ -836,7 +855,7 @@ def _prepare_movement(self, position, velocity=None): # TODO Adapt interface to use "in_range"? self._target_pos[axis] = position[axis] - self.log.debug(f"New target pos: {self._target_pos}") + #self.log.debug(f"New target pos: {self._target_pos}") # TODO Add max velocity as a hardware constraint/ Calculate from scan_freq etc? if velocity is None: @@ -931,11 +950,10 @@ def is_full(self): class NiScanningProbeInterfuse(CoordinateTransformMixin, NiScanningProbeInterfuseBare): - def _initialize_ni_scan_arrays(self, scan_data): - scan_coords = self._get_scan_lines(scan_data) - scan_coords_transf = self.coordinate_transform(scan_coords, inverse=False) - scan_voltages_transformed = {self._ni_channel_mapping[ax]: self._position_to_voltage(ax, val) - for ax, val in scan_coords_transf.items()} + def _init_scan_grid(self, scan_data): + + scan_coords_transf = self.coordinate_transform(super()._init_scan_grid(scan_data), inverse=False) + + return scan_coords_transf - return scan_voltages_transformed From 019945ada24df9728377e3d4f60387fa0f1004b3 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 2 Feb 2024 14:52:20 +0100 Subject: [PATCH 78/81] (iteratively) shrink scan ranges if out-of-bounds, eg. due to tilt correction --- .../interfuse/ni_scanning_probe_interfuse.py | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 817fc48fe..e5da7c3ad 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -255,20 +255,15 @@ def configure_scan(self, scan_settings): return True, self.scan_settings with self._thread_lock_data: try: - self._scan_data = ScanData( - channels=tuple(self._constraints.channels.values()), - scan_axes=tuple(self._constraints.axes[ax] for ax in axes), - scan_range=ranges, - scan_resolution=tuple(resolution), - scan_frequency=frequency, - position_feedback_axes=None - ) + + self._scan_data = self._create_scan_data(axes, ranges, resolution, frequency) + self.raw_data_container = RawDataContainer(self._scan_data.channels, - resolution[ - 1] if self._scan_data.scan_dimension == 2 else 1, + resolution[1] if self._scan_data.scan_dimension==2 else 1, resolution[0], self.__backwards_line_resolution) - # self.log.debug(f"New scanData created: {self._scan_data.data}") + + ni_scan_dict = self._init_ni_scan_arrays(self._scan_data) except: self.log.exception("") @@ -283,9 +278,6 @@ def configure_scan(self, scan_settings): ) self._ni_finite_sampling_io().set_output_mode(SamplingOutputMode.JUMP_LIST) - - ni_scan_dict = self._init_ni_scan_arrays(self._scan_data) - self._ni_finite_sampling_io().set_frame_data(ni_scan_dict) except: @@ -714,6 +706,47 @@ def _check_scan_grid(self, scan_coords): if out_of_range: raise ValueError(f"Scan axis {ax} out of range [{position_min}, {position_max}]") + def _create_scan_data(self, axes, ranges, resolution, frequency): + + valid_scan_grid = False + i_trial, n_max_trials = 0, 25 + + while not valid_scan_grid and i_trial < n_max_trials: + + if i_trial > 0: + ranges = self._shrink_scan_ranges(ranges) + + scan_data = ScanData( + channels=tuple(self._constraints.channels.values()), + scan_axes=tuple(self._constraints.axes[ax] for ax in axes), + scan_range=ranges, + scan_resolution=tuple(resolution), + scan_frequency=frequency, + position_feedback_axes=None) + + try: + ni_scan_dict = self._init_ni_scan_arrays(scan_data) + valid_scan_grid = True + except ValueError: + valid_scan_grid = False + + i_trial += 1 + + if not valid_scan_grid: + raise ValueError("Couldn't create scan grid. ") + + if i_trial > 1: + self.log.warning(f"Adapted out-of-bounds scan range to {ranges}") + + # self.log.debug(f"New scanData created: {self._scan_data.data}") + return scan_data + + + def _shrink_scan_ranges(self, ranges, factor=0.01): + lenghts = [stop - start for (start, stop) in ranges] + + return [(start + 0.01 * lenghts[idx], stop - 0.01 * lenghts[idx]) for idx, (start, stop) in enumerate(ranges)] + def _init_ni_scan_arrays(self, scan_data): """ @param ScanData scan_data: The desired ScanData instance From c548402a68c77435fb9a6057c1f4ac666f2a6ca4 Mon Sep 17 00:00:00 2001 From: moe Date: Fri, 2 Feb 2024 15:00:46 +0100 Subject: [PATCH 79/81] fix typo --- src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index e5da7c3ad..99842206f 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -745,7 +745,7 @@ def _create_scan_data(self, axes, ranges, resolution, frequency): def _shrink_scan_ranges(self, ranges, factor=0.01): lenghts = [stop - start for (start, stop) in ranges] - return [(start + 0.01 * lenghts[idx], stop - 0.01 * lenghts[idx]) for idx, (start, stop) in enumerate(ranges)] + return [(start + factor* lenghts[idx], stop - factor* lenghts[idx]) for idx, (start, stop) in enumerate(ranges)] def _init_ni_scan_arrays(self, scan_data): """ From 590cbc2a5a5cbd3cb04821893daf82a0d50246b5 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 9 Feb 2024 09:52:02 +0100 Subject: [PATCH 80/81] update docs and changelog --- docs/changelog.md | 1 + docs/setup_confocal_scanning.md | 10 ++++++++++ .../hardware/interfuse/ni_scanning_probe_interfuse.py | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 2183afe42..0112fe4f7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,7 @@ ### Bugfixes ### New Features +- Re-introduced tilt correction (from old core) to the scanning probe toolchain. - Improved support for Stanford Research Systems signal generators ### Other diff --git a/docs/setup_confocal_scanning.md b/docs/setup_confocal_scanning.md index 44f99b034..b489f33e9 100644 --- a/docs/setup_confocal_scanning.md +++ b/docs/setup_confocal_scanning.md @@ -186,4 +186,14 @@ docstring of every module's python file. In the list above, a direct link for ev - The scanning gui's `optimizer_plot_dimensions` ConfigOption allows to specify the optimizer's scanning behavior. The default setting `[2,1]` enables one 2D and one 1D optimization step. You may set to eg. `[2,2,2]` to have three two-dimensionsal scans done for optimzation. In the gui (Settings/Optimizer Settings), this will change the list of possible optimizer sequences. - The maximum scanning frequency is given by the bandwidth of your Piezo controller (check the datasheet). It might make sense to put an even smaller limit into your config, since scanning at the hardware limit might introduce artifacts/offsets to your confocal scan. +# Tilt correction + +The above configuration will enable the tilt correction feature for the ScanningProbeDummy and NiScanningProbeInterfuse. +This allows to perform scans in tilted layers, eg. along the surface of a non-flat sample. +- In the scanning_probe_gui, you can configure this feature in the menu enabled by 'View' -> 'Tilt correction'. +- Choose three support vectors in the plane that should become the new e_z plane. + Instead of manually typing the coordinates of a support vector, hitting the 'Vec 1" button will + insert the current crosshair position as support vector 1. +- Enable the transformation by the "Tilt correction" button. + # Todo this readme: diff --git a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py index 99842206f..e894b3b5d 100644 --- a/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py +++ b/src/qudi/hardware/interfuse/ni_scanning_probe_interfuse.py @@ -46,7 +46,9 @@ class NiScanningProbeInterfuseBare(ScanningProbeInterface): Example config for copy-paste: ni_scanning_probe: - module.Class: 'interfuse.ni_scanning_probe_interfuse.NiScanningProbeInterfuseBare' + module.Class: 'interfuse.ni_scanning_probe_interfuse.NiScanningProbeInterfuse' + # to use without tilt correction + # module.Class: 'interfuse.ni_scanning_probe_interfuse.NiScanningProbeInterfuseBare' connect: scan_hardware: 'ni_finite_sampling_io' analog_output: 'ni_ao' From 1cb51fd2a128ff3a81e9b10bd5542dd16008c97c Mon Sep 17 00:00:00 2001 From: moe Date: Fri, 9 Feb 2024 09:56:19 +0100 Subject: [PATCH 81/81] update docs --- docs/setup_confocal_scanning.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/setup_confocal_scanning.md b/docs/setup_confocal_scanning.md index b489f33e9..794dcb251 100644 --- a/docs/setup_confocal_scanning.md +++ b/docs/setup_confocal_scanning.md @@ -182,7 +182,7 @@ docstring of every module's python file. In the list above, a direct link for ev max_channel_samples_buffer: 10000000 # optional read_write_timeout: 10 # optional -# Configuration hints: +# Configuration hints - The scanning gui's `optimizer_plot_dimensions` ConfigOption allows to specify the optimizer's scanning behavior. The default setting `[2,1]` enables one 2D and one 1D optimization step. You may set to eg. `[2,2,2]` to have three two-dimensionsal scans done for optimzation. In the gui (Settings/Optimizer Settings), this will change the list of possible optimizer sequences. - The maximum scanning frequency is given by the bandwidth of your Piezo controller (check the datasheet). It might make sense to put an even smaller limit into your config, since scanning at the hardware limit might introduce artifacts/offsets to your confocal scan. @@ -191,9 +191,9 @@ docstring of every module's python file. In the list above, a direct link for ev The above configuration will enable the tilt correction feature for the ScanningProbeDummy and NiScanningProbeInterfuse. This allows to perform scans in tilted layers, eg. along the surface of a non-flat sample. - In the scanning_probe_gui, you can configure this feature in the menu enabled by 'View' -> 'Tilt correction'. -- Choose three support vectors in the plane that should become the new e_z plane. +- Choose three support vectors in the plane that should become the new $\hat{e}_z$ plane. Instead of manually typing the coordinates of a support vector, hitting the 'Vec 1" button will insert the current crosshair position as support vector 1. - Enable the transformation by the "Tilt correction" button. -# Todo this readme: +# Todo this readme