1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
""" |
3
|
|
|
This file contains the Qudi logic which controls all pulsed measurements. |
4
|
|
|
|
5
|
|
|
Qudi is free software: you can redistribute it and/or modify |
6
|
|
|
it under the terms of the GNU General Public License as published by |
7
|
|
|
the Free Software Foundation, either version 3 of the License, or |
8
|
|
|
(at your option) any later version. |
9
|
|
|
|
10
|
|
|
Qudi is distributed in the hope that it will be useful, |
11
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
12
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13
|
|
|
GNU General Public License for more details. |
14
|
|
|
|
15
|
|
|
You should have received a copy of the GNU General Public License |
16
|
|
|
along with Qudi. If not, see <http://www.gnu.org/licenses/>. |
17
|
|
|
|
18
|
|
|
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the |
19
|
|
|
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/> |
20
|
|
|
""" |
21
|
|
|
|
22
|
|
|
from qtpy import QtCore |
23
|
|
|
from collections import OrderedDict |
24
|
|
|
import numpy as np |
25
|
|
|
import time |
26
|
|
|
import datetime |
27
|
|
|
import matplotlib.pyplot as plt |
28
|
|
|
|
29
|
|
|
from core.module import Connector, ConfigOption, StatusVar |
30
|
|
|
from core.util.mutex import Mutex |
31
|
|
|
from core.util.network import netobtain |
32
|
|
|
from core.util import units |
33
|
|
|
from logic.generic_logic import GenericLogic |
34
|
|
|
from logic.pulsed.pulse_extractor import PulseExtractor |
35
|
|
|
from logic.pulsed.pulse_analyzer import PulseAnalyzer |
36
|
|
|
|
37
|
|
|
|
38
|
|
|
class PulsedMeasurementLogic(GenericLogic): |
39
|
|
|
""" |
40
|
|
|
This is the Logic class for the control of pulsed measurements. |
41
|
|
|
""" |
42
|
|
|
_modclass = 'PulsedMeasurementLogic' |
43
|
|
|
_modtype = 'logic' |
44
|
|
|
|
45
|
|
|
## declare connectors |
46
|
|
|
fitlogic = Connector(interface='FitLogic') |
47
|
|
|
savelogic = Connector(interface='SaveLogic') |
48
|
|
|
fastcounter = Connector(interface='FastCounterInterface') |
49
|
|
|
microwave = Connector(interface='MWInterface') |
50
|
|
|
pulsegenerator = Connector(interface='PulserInterface') |
51
|
|
|
|
52
|
|
|
# Config options |
53
|
|
|
# Optional additional paths to import from |
54
|
|
|
extraction_import_path = ConfigOption(name='additional_extraction_path', default=None) |
55
|
|
|
analysis_import_path = ConfigOption(name='additional_analysis_path', default=None) |
56
|
|
|
# Optional file type descriptor for saving raw data to file |
57
|
|
|
_raw_data_save_type = ConfigOption(name='raw_data_save_type', default='text') |
58
|
|
|
|
59
|
|
|
# status variables |
60
|
|
|
# ext. microwave settings |
61
|
|
|
__microwave_power = StatusVar(default=-30.0) |
62
|
|
|
__microwave_freq = StatusVar(default=2870e6) |
63
|
|
|
__use_ext_microwave = StatusVar(default=False) |
64
|
|
|
|
65
|
|
|
# fast counter settings |
66
|
|
|
__fast_counter_record_length = StatusVar(default=3.0e-6) |
67
|
|
|
__fast_counter_binwidth = StatusVar(default=1.0e-9) |
68
|
|
|
__fast_counter_gates = StatusVar(default=0) |
69
|
|
|
|
70
|
|
|
# measurement timer settings |
71
|
|
|
__timer_interval = StatusVar(default=5) |
72
|
|
|
|
73
|
|
|
# Pulsed measurement settings |
74
|
|
|
_invoke_settings_from_sequence = StatusVar(default=False) |
75
|
|
|
_number_of_lasers = StatusVar(default=50) |
76
|
|
|
_controlled_variable = StatusVar(default=list(range(50))) |
77
|
|
|
_alternating = StatusVar(default=False) |
78
|
|
|
_laser_ignore_list = StatusVar(default=list()) |
79
|
|
|
_data_units = StatusVar(default=('s', '')) |
80
|
|
|
|
81
|
|
|
# PulseExtractor settings |
82
|
|
|
extraction_parameters = StatusVar(default=None) |
83
|
|
|
analysis_parameters = StatusVar(default=None) |
84
|
|
|
|
85
|
|
|
# Container to store measurement information about the currently loaded sequence |
86
|
|
|
_measurement_information = StatusVar(default=dict()) |
87
|
|
|
# Container to store information about the sampled waveform/sequence currently loaded |
88
|
|
|
_sampling_information = StatusVar(default=dict()) |
89
|
|
|
|
90
|
|
|
# alternative signal computation settings: |
91
|
|
|
_alternative_data_type = StatusVar(default=None) |
92
|
|
|
zeropad = StatusVar(default=0) |
93
|
|
|
psd = StatusVar(default=False) |
94
|
|
|
window = StatusVar(default='none') |
95
|
|
|
base_corr = StatusVar(default=True) |
96
|
|
|
|
97
|
|
|
# notification signals for master module (i.e. GUI) |
98
|
|
|
sigMeasurementDataUpdated = QtCore.Signal() |
99
|
|
|
sigTimerUpdated = QtCore.Signal(float, int, float) |
100
|
|
|
sigFitUpdated = QtCore.Signal(str, np.ndarray, object) |
101
|
|
|
sigMeasurementStatusUpdated = QtCore.Signal(bool, bool) |
102
|
|
|
sigPulserRunningUpdated = QtCore.Signal(bool) |
103
|
|
|
sigExtMicrowaveRunningUpdated = QtCore.Signal(bool) |
104
|
|
|
sigExtMicrowaveSettingsUpdated = QtCore.Signal(dict) |
105
|
|
|
sigFastCounterSettingsUpdated = QtCore.Signal(dict) |
106
|
|
|
sigMeasurementSettingsUpdated = QtCore.Signal(dict) |
107
|
|
|
sigAnalysisSettingsUpdated = QtCore.Signal(dict) |
108
|
|
|
sigExtractionSettingsUpdated = QtCore.Signal(dict) |
109
|
|
|
# Internal signals |
110
|
|
|
sigStartTimer = QtCore.Signal() |
111
|
|
|
sigStopTimer = QtCore.Signal() |
112
|
|
|
|
113
|
|
|
def __init__(self, config, **kwargs): |
114
|
|
|
super().__init__(config=config, **kwargs) |
115
|
|
|
|
116
|
|
|
self.log.debug('The following configuration was found.') |
117
|
|
|
# checking for the right configuration |
118
|
|
|
for key in config.keys(): |
119
|
|
|
self.log.debug('{0}: {1}'.format(key, config[key])) |
120
|
|
|
|
121
|
|
|
# timer for measurement |
122
|
|
|
self.__analysis_timer = None |
123
|
|
|
self.__start_time = 0 |
124
|
|
|
self.__elapsed_time = 0 |
125
|
|
|
self.__elapsed_sweeps = 0 # FIXME: unused |
126
|
|
|
|
127
|
|
|
# threading |
128
|
|
|
self._threadlock = Mutex() |
129
|
|
|
|
130
|
|
|
# measurement data |
131
|
|
|
self.signal_data = np.empty((2, 0), dtype=float) |
132
|
|
|
self.signal_alt_data = np.empty((2, 0), dtype=float) |
133
|
|
|
self.measurement_error = np.empty((2, 0), dtype=float) |
134
|
|
|
self.laser_data = np.zeros((10, 20), dtype='int64') |
135
|
|
|
self.raw_data = np.zeros((10, 20), dtype='int64') |
136
|
|
|
|
137
|
|
|
self._saved_raw_data = OrderedDict() # temporary saved raw data |
138
|
|
|
self._recalled_raw_data_tag = None # the currently recalled raw data dict key |
139
|
|
|
|
140
|
|
|
# Paused measurement flag |
141
|
|
|
self.__is_paused = False |
142
|
|
|
|
143
|
|
|
# for fit: |
144
|
|
|
self.fc = None # Fit container |
145
|
|
|
self.signal_fit_data = np.empty((2, 0), dtype=float) # The x,y data of the fit result |
146
|
|
|
return |
147
|
|
|
|
148
|
|
|
def on_activate(self): |
149
|
|
|
""" Initialisation performed during activation of the module. |
150
|
|
|
""" |
151
|
|
|
# Create an instance of PulseExtractor |
152
|
|
|
self._pulseextractor = PulseExtractor(pulsedmeasurementlogic=self) |
153
|
|
|
self._pulseanalyzer = PulseAnalyzer(pulsedmeasurementlogic=self) |
154
|
|
|
|
155
|
|
|
# QTimer must be created here instead of __init__ because otherwise the timer will not run |
156
|
|
|
# in this logic's thread but in the manager instead. |
157
|
|
|
self.__analysis_timer = QtCore.QTimer() |
158
|
|
|
self.__analysis_timer.setSingleShot(False) |
159
|
|
|
self.__analysis_timer.setInterval(round(1000. * self.__timer_interval)) |
160
|
|
|
self.__analysis_timer.timeout.connect(self._pulsed_analysis_loop, |
161
|
|
|
QtCore.Qt.QueuedConnection) |
162
|
|
|
|
163
|
|
|
# Fitting |
164
|
|
|
self.fc = self.fitlogic().make_fit_container('pulsed', '1d') |
165
|
|
|
self.fc.set_units(['s', 'arb.u.']) |
166
|
|
|
|
167
|
|
|
# Recall saved status variables |
168
|
|
|
if 'fits' in self._statusVariables and isinstance(self._statusVariables.get('fits'), dict): |
169
|
|
|
self.fc.load_from_dict(self._statusVariables['fits']) |
170
|
|
|
|
171
|
|
|
# Turn off pulse generator |
172
|
|
|
self.pulse_generator_off() |
173
|
|
|
|
174
|
|
|
# Check and configure fast counter |
175
|
|
|
binning_constraints = self.fastcounter().get_constraints()['hardware_binwidth_list'] |
176
|
|
|
if self.__fast_counter_binwidth not in binning_constraints: |
177
|
|
|
self.__fast_counter_binwidth = binning_constraints[0] |
178
|
|
|
if self.__fast_counter_record_length <= 0: |
179
|
|
|
self.__fast_counter_record_length = 3e-6 |
180
|
|
|
self.fast_counter_off() |
181
|
|
|
self.set_fast_counter_settings() |
182
|
|
|
|
183
|
|
|
# Check and configure external microwave |
184
|
|
|
if self.__use_ext_microwave: |
185
|
|
|
self.microwave_off() |
186
|
|
|
self.set_microwave_settings(frequency=self.__microwave_freq, |
187
|
|
|
power=self.__microwave_power, |
188
|
|
|
use_ext_microwave=True) |
189
|
|
|
|
190
|
|
|
# Convert controlled variable list into numpy.ndarray |
191
|
|
|
self._controlled_variable = np.array(self._controlled_variable, dtype=float) |
192
|
|
|
|
193
|
|
|
# initialize arrays for the measurement data |
194
|
|
|
self._initialize_data_arrays() |
195
|
|
|
|
196
|
|
|
# recalled saved raw data dict key |
197
|
|
|
self._recalled_raw_data_tag = None |
198
|
|
|
|
199
|
|
|
# Connect internal signals |
200
|
|
|
self.sigStartTimer.connect(self.__analysis_timer.start, QtCore.Qt.QueuedConnection) |
201
|
|
|
self.sigStopTimer.connect(self.__analysis_timer.stop, QtCore.Qt.QueuedConnection) |
202
|
|
|
return |
203
|
|
|
|
204
|
|
|
def on_deactivate(self): |
205
|
|
|
""" Deactivate the module properly. |
206
|
|
|
""" |
207
|
|
|
if self.module_state() == 'locked': |
208
|
|
|
self.stop_pulsed_measurement() |
209
|
|
|
|
210
|
|
|
self._statusVariables['_controlled_variable'] = list(self._controlled_variable) |
211
|
|
|
if len(self.fc.fit_list) > 0: |
212
|
|
|
self._statusVariables['fits'] = self.fc.save_to_dict() |
213
|
|
|
|
214
|
|
|
self.extraction_parameters = self._pulseextractor.full_settings_dict |
215
|
|
|
self.analysis_parameters = self._pulseanalyzer.full_settings_dict |
216
|
|
|
|
217
|
|
|
self.__analysis_timer.timeout.disconnect() |
218
|
|
|
self.sigStartTimer.disconnect() |
219
|
|
|
self.sigStopTimer.disconnect() |
220
|
|
|
return |
221
|
|
|
|
222
|
|
|
############################################################################ |
223
|
|
|
# Fast counter control methods and properties |
224
|
|
|
############################################################################ |
225
|
|
|
@property |
226
|
|
|
def fast_counter_settings(self): |
227
|
|
|
settings_dict = dict() |
228
|
|
|
settings_dict['bin_width'] = float(self.__fast_counter_binwidth) |
229
|
|
|
settings_dict['record_length'] = float(self.__fast_counter_record_length) |
230
|
|
|
settings_dict['number_of_gates'] = int(self.__fast_counter_gates) |
231
|
|
|
settings_dict['is_gated'] = bool(self.fastcounter().is_gated()) |
232
|
|
|
return settings_dict |
233
|
|
|
|
234
|
|
|
@fast_counter_settings.setter |
235
|
|
|
def fast_counter_settings(self, settings_dict): |
236
|
|
|
if isinstance(settings_dict, dict): |
237
|
|
|
self.set_fast_counter_settings(settings_dict) |
238
|
|
|
return |
239
|
|
|
|
240
|
|
|
@property |
241
|
|
|
def fastcounter_constraints(self): |
242
|
|
|
return self.fastcounter().get_constraints() |
243
|
|
|
|
244
|
|
|
@QtCore.Slot(dict) |
245
|
|
|
def set_fast_counter_settings(self, settings_dict=None, **kwargs): |
246
|
|
|
""" |
247
|
|
|
Either accept a settings dictionary as positional argument or keyword arguments. |
248
|
|
|
If both are present both are being used by updating the settings_dict with kwargs. |
249
|
|
|
The keyword arguments take precedence over the items in settings_dict if there are |
250
|
|
|
conflicting names. |
251
|
|
|
|
252
|
|
|
@param settings_dict: |
253
|
|
|
@param kwargs: |
254
|
|
|
@return: |
255
|
|
|
""" |
256
|
|
|
# Check if fast counter is running and do nothing if that is the case |
257
|
|
|
counter_status = self.fastcounter().get_status() |
258
|
|
|
if not counter_status >= 2 and not counter_status < 0: |
259
|
|
|
# Determine complete settings dictionary |
260
|
|
|
if not isinstance(settings_dict, dict): |
261
|
|
|
settings_dict = kwargs |
262
|
|
|
else: |
263
|
|
|
settings_dict.update(kwargs) |
264
|
|
|
|
265
|
|
|
# Set parameters if present |
266
|
|
|
if 'bin_width' in settings_dict: |
267
|
|
|
self.__fast_counter_binwidth = float(settings_dict['bin_width']) |
268
|
|
|
if 'record_length' in settings_dict: |
269
|
|
|
self.__fast_counter_record_length = float(settings_dict['record_length']) |
270
|
|
|
if 'number_of_gates' in settings_dict: |
271
|
|
|
if self.fastcounter().is_gated(): |
272
|
|
|
self.__fast_counter_gates = int(settings_dict['number_of_gates']) |
273
|
|
|
else: |
274
|
|
|
self.__fast_counter_gates = 0 |
275
|
|
|
|
276
|
|
|
# Apply the settings to hardware |
277
|
|
|
self.__fast_counter_binwidth, \ |
278
|
|
|
self.__fast_counter_record_length, \ |
279
|
|
|
self.__fast_counter_gates = self.fastcounter().configure(self.__fast_counter_binwidth, |
280
|
|
|
self.__fast_counter_record_length, |
281
|
|
|
self.__fast_counter_gates) |
282
|
|
|
else: |
283
|
|
|
self.log.warning('Fast counter is not idle (status: {0}).\n' |
284
|
|
|
'Unable to apply new settings.'.format(counter_status)) |
285
|
|
|
|
286
|
|
|
# emit update signal for master (GUI or other logic module) |
287
|
|
|
self.sigFastCounterSettingsUpdated.emit(self.fast_counter_settings) |
288
|
|
|
return self.fast_counter_settings |
289
|
|
|
|
290
|
|
|
def fast_counter_on(self): |
291
|
|
|
"""Switching on the fast counter |
292
|
|
|
|
293
|
|
|
@return int: error code (0:OK, -1:error) |
294
|
|
|
""" |
295
|
|
|
return self.fastcounter().start_measure() |
296
|
|
|
|
297
|
|
|
def fast_counter_off(self): |
298
|
|
|
"""Switching off the fast counter |
299
|
|
|
|
300
|
|
|
@return int: error code (0:OK, -1:error) |
301
|
|
|
""" |
302
|
|
|
return self.fastcounter().stop_measure() |
303
|
|
|
|
304
|
|
|
@QtCore.Slot(bool) |
305
|
|
|
def toggle_fast_counter(self, switch_on): |
306
|
|
|
""" |
307
|
|
|
""" |
308
|
|
|
if not isinstance(switch_on, bool): |
309
|
|
|
return -1 |
310
|
|
|
|
311
|
|
|
if switch_on: |
312
|
|
|
err = self.fast_counter_on() |
313
|
|
|
else: |
314
|
|
|
err = self.fast_counter_off() |
315
|
|
|
return err |
316
|
|
|
|
317
|
|
|
def fast_counter_pause(self): |
318
|
|
|
"""Switching off the fast counter |
319
|
|
|
|
320
|
|
|
@return int: error code (0:OK, -1:error) |
321
|
|
|
""" |
322
|
|
|
return self.fastcounter().pause_measure() |
323
|
|
|
|
324
|
|
|
def fast_counter_continue(self): |
325
|
|
|
"""Switching off the fast counter |
326
|
|
|
|
327
|
|
|
@return int: error code (0:OK, -1:error) |
328
|
|
|
""" |
329
|
|
|
return self.fastcounter().continue_measure() |
330
|
|
|
|
331
|
|
|
@QtCore.Slot(bool) |
332
|
|
|
def fast_counter_pause_continue(self, continue_counter): |
333
|
|
|
""" |
334
|
|
|
""" |
335
|
|
|
if not isinstance(continue_counter, bool): |
336
|
|
|
return -1 |
337
|
|
|
|
338
|
|
|
if continue_counter: |
339
|
|
|
err = self.fast_counter_continue() |
340
|
|
|
else: |
341
|
|
|
err = self.fast_counter_pause() |
342
|
|
|
return err |
343
|
|
|
############################################################################ |
344
|
|
|
|
345
|
|
|
############################################################################ |
346
|
|
|
# External microwave control methods and properties |
347
|
|
|
############################################################################ |
348
|
|
|
@property |
349
|
|
|
def ext_microwave_settings(self): |
350
|
|
|
settings_dict = dict() |
351
|
|
|
settings_dict['power'] = float(self.__microwave_power) |
352
|
|
|
settings_dict['frequency'] = float(self.__microwave_freq) |
353
|
|
|
settings_dict['use_ext_microwave'] = bool(self.__use_ext_microwave) |
354
|
|
|
return settings_dict |
355
|
|
|
|
356
|
|
|
@ext_microwave_settings.setter |
357
|
|
|
def ext_microwave_settings(self, settings_dict): |
358
|
|
|
if isinstance(settings_dict, dict): |
359
|
|
|
self.set_microwave_settings(settings_dict) |
360
|
|
|
return |
361
|
|
|
|
362
|
|
|
@property |
363
|
|
|
def ext_microwave_constraints(self): |
364
|
|
|
return self.microwave().get_limits() |
365
|
|
|
|
366
|
|
|
def microwave_on(self): |
367
|
|
|
""" |
368
|
|
|
Turns the external (CW) microwave output on. |
369
|
|
|
|
370
|
|
|
:return int: error code (0:OK, -1:error) |
371
|
|
|
""" |
372
|
|
|
err = self.microwave().cw_on() |
373
|
|
|
if err < 0: |
374
|
|
|
self.log.error('Failed to turn on external CW microwave output.') |
375
|
|
|
self.sigExtMicrowaveRunningUpdated.emit(self.microwave().get_status()[1]) |
376
|
|
|
return err |
377
|
|
|
|
378
|
|
|
def microwave_off(self): |
379
|
|
|
""" |
380
|
|
|
Turns the external (CW) microwave output off. |
381
|
|
|
|
382
|
|
|
:return int: error code (0:OK, -1:error) |
383
|
|
|
""" |
384
|
|
|
err = self.microwave().off() |
385
|
|
|
if err < 0: |
386
|
|
|
self.log.error('Failed to turn off external CW microwave output.') |
387
|
|
|
self.sigExtMicrowaveRunningUpdated.emit(self.microwave().get_status()[1]) |
388
|
|
|
return err |
389
|
|
|
|
390
|
|
|
@QtCore.Slot(bool) |
391
|
|
|
def toggle_microwave(self, switch_on): |
392
|
|
|
""" |
393
|
|
|
Turn the external microwave output on/off. |
394
|
|
|
|
395
|
|
|
:param switch_on: bool, turn microwave on (True) or off (False) |
396
|
|
|
:return int: error code (0:OK, -1:error) |
397
|
|
|
""" |
398
|
|
|
if not isinstance(switch_on, bool): |
399
|
|
|
return -1 |
400
|
|
|
|
401
|
|
|
if switch_on: |
402
|
|
|
err = self.microwave_on() |
403
|
|
|
else: |
404
|
|
|
err = self.microwave_off() |
405
|
|
|
return err |
406
|
|
|
|
407
|
|
|
@QtCore.Slot(dict) |
408
|
|
|
def set_microwave_settings(self, settings_dict=None, **kwargs): |
409
|
|
|
""" |
410
|
|
|
Apply new settings to the external microwave device. |
411
|
|
|
Either accept a settings dictionary as positional argument or keyword arguments. |
412
|
|
|
If both are present both are being used by updating the settings_dict with kwargs. |
413
|
|
|
The keyword arguments take precedence over the items in settings_dict if there are |
414
|
|
|
conflicting names. |
415
|
|
|
|
416
|
|
|
@param settings_dict: |
417
|
|
|
@param kwargs: |
418
|
|
|
@return: |
419
|
|
|
""" |
420
|
|
|
# Check if microwave is running and do nothing if that is the case |
421
|
|
|
if self.microwave().get_status()[1]: |
422
|
|
|
self.log.warning('Microwave device is running.\nUnable to apply new settings.') |
423
|
|
|
else: |
424
|
|
|
# Determine complete settings dictionary |
425
|
|
|
if not isinstance(settings_dict, dict): |
426
|
|
|
settings_dict = kwargs |
427
|
|
|
else: |
428
|
|
|
settings_dict.update(kwargs) |
429
|
|
|
|
430
|
|
|
# Set parameters if present |
431
|
|
|
if 'power' in settings_dict: |
432
|
|
|
self.__microwave_power = float(settings_dict['power']) |
433
|
|
|
if 'frequency' in settings_dict: |
434
|
|
|
self.__microwave_freq = float(settings_dict['frequency']) |
435
|
|
|
if 'use_ext_microwave' in settings_dict: |
436
|
|
|
self.__use_ext_microwave = bool(settings_dict['use_ext_microwave']) |
437
|
|
|
|
438
|
|
|
if self.__use_ext_microwave: |
439
|
|
|
# Apply the settings to hardware |
440
|
|
|
self.__microwave_freq, \ |
441
|
|
|
self.__microwave_power, \ |
442
|
|
|
dummy = self.microwave().set_cw(frequency=self.__microwave_freq, |
443
|
|
|
power=self.__microwave_power) |
444
|
|
|
|
445
|
|
|
# emit update signal for master (GUI or other logic module) |
446
|
|
|
self.sigExtMicrowaveSettingsUpdated.emit({'power': self.__microwave_power, |
447
|
|
|
'frequency': self.__microwave_freq, |
448
|
|
|
'use_ext_microwave': self.__use_ext_microwave}) |
449
|
|
|
return self.__microwave_freq, self.__microwave_power, self.__use_ext_microwave |
450
|
|
|
############################################################################ |
451
|
|
|
|
452
|
|
|
############################################################################ |
453
|
|
|
# Pulse generator control methods and properties |
454
|
|
|
############################################################################ |
455
|
|
|
@property |
456
|
|
|
def pulse_generator_constraints(self): |
457
|
|
|
return self.pulsegenerator().get_constraints() |
458
|
|
|
|
459
|
|
|
def pulse_generator_on(self): |
460
|
|
|
"""Switching on the pulse generator. """ |
461
|
|
|
err = self.pulsegenerator().pulser_on() |
462
|
|
|
if err < 0: |
463
|
|
|
self.log.error('Failed to turn on pulse generator output.') |
464
|
|
|
self.sigPulserRunningUpdated.emit(False) |
465
|
|
|
else: |
466
|
|
|
self.sigPulserRunningUpdated.emit(True) |
467
|
|
|
return err |
468
|
|
|
|
469
|
|
|
def pulse_generator_off(self): |
470
|
|
|
"""Switching off the pulse generator. """ |
471
|
|
|
err = self.pulsegenerator().pulser_off() |
472
|
|
|
if err < 0: |
473
|
|
|
self.log.error('Failed to turn off pulse generator output.') |
474
|
|
|
self.sigPulserRunningUpdated.emit(True) |
475
|
|
|
else: |
476
|
|
|
self.sigPulserRunningUpdated.emit(False) |
477
|
|
|
return err |
478
|
|
|
|
479
|
|
|
@QtCore.Slot(bool) |
480
|
|
|
def toggle_pulse_generator(self, switch_on): |
481
|
|
|
""" |
482
|
|
|
Switch the pulse generator on or off. |
483
|
|
|
|
484
|
|
|
:param switch_on: bool, turn the pulse generator on (True) or off (False) |
485
|
|
|
:return int: error code (0: OK, -1: error) |
486
|
|
|
""" |
487
|
|
|
if not isinstance(switch_on, bool): |
488
|
|
|
return -1 |
489
|
|
|
|
490
|
|
|
if switch_on: |
491
|
|
|
err = self.pulse_generator_on() |
492
|
|
|
else: |
493
|
|
|
err = self.pulse_generator_off() |
494
|
|
|
return err |
495
|
|
|
############################################################################ |
496
|
|
|
|
497
|
|
|
############################################################################ |
498
|
|
|
# Measurement control methods and properties |
499
|
|
|
############################################################################ |
500
|
|
|
@property |
501
|
|
|
def measurement_settings(self): |
502
|
|
|
settings_dict = dict() |
503
|
|
|
settings_dict['invoke_settings'] = bool(self._invoke_settings_from_sequence) |
504
|
|
|
settings_dict['controlled_variable'] = np.array(self._controlled_variable, |
505
|
|
|
dtype=float).copy() |
506
|
|
|
settings_dict['number_of_lasers'] = int(self._number_of_lasers) |
507
|
|
|
settings_dict['laser_ignore_list'] = list(self._laser_ignore_list).copy() |
508
|
|
|
settings_dict['alternating'] = bool(self._alternating) |
509
|
|
|
settings_dict['units'] = self._data_units |
510
|
|
|
return settings_dict |
511
|
|
|
|
512
|
|
|
@measurement_settings.setter |
513
|
|
|
def measurement_settings(self, settings_dict): |
514
|
|
|
if isinstance(settings_dict, dict): |
515
|
|
|
self.set_measurement_settings(settings_dict) |
516
|
|
|
return |
517
|
|
|
|
518
|
|
|
@property |
519
|
|
|
def measurement_information(self): |
520
|
|
|
return self._measurement_information |
521
|
|
|
|
522
|
|
|
@measurement_information.setter |
523
|
|
|
def measurement_information(self, info_dict): |
524
|
|
|
# Check if mandatory params to invoke settings are missing and set empty dict in that case. |
525
|
|
|
mand_params = ('number_of_lasers', |
526
|
|
|
'controlled_variable', |
527
|
|
|
'laser_ignore_list', |
528
|
|
|
'alternating', |
529
|
|
|
'counting_length') |
530
|
|
|
if not isinstance(info_dict, dict) or not all(param in info_dict for param in mand_params): |
531
|
|
|
self._measurement_information = dict() |
532
|
|
|
return |
533
|
|
|
|
534
|
|
|
# Set measurement_information dict |
535
|
|
|
self._measurement_information = info_dict.copy() |
536
|
|
|
|
537
|
|
|
# invoke settings if needed |
538
|
|
|
if self._invoke_settings_from_sequence and self._measurement_information: |
539
|
|
|
self._apply_invoked_settings() |
540
|
|
|
self.sigMeasurementSettingsUpdated.emit(self.measurement_settings) |
541
|
|
|
return |
542
|
|
|
|
543
|
|
|
@property |
544
|
|
|
def sampling_information(self): |
545
|
|
|
return self._sampling_information |
546
|
|
|
|
547
|
|
|
@sampling_information.setter |
548
|
|
|
def sampling_information(self, info_dict): |
549
|
|
|
if isinstance(info_dict, dict): |
550
|
|
|
self._sampling_information = info_dict |
551
|
|
|
else: |
552
|
|
|
self._sampling_information = dict() |
553
|
|
|
return |
554
|
|
|
|
555
|
|
|
@property |
556
|
|
|
def timer_interval(self): |
557
|
|
|
return float(self.__timer_interval) |
558
|
|
|
|
559
|
|
|
@timer_interval.setter |
560
|
|
|
def timer_interval(self, value): |
561
|
|
|
if isinstance(value, (int, float)): |
562
|
|
|
self.set_timer_interval(value) |
563
|
|
|
return |
564
|
|
|
|
565
|
|
|
@property |
566
|
|
|
def alternative_data_type(self): |
567
|
|
|
return str(self._alternative_data_type) |
568
|
|
|
|
569
|
|
|
@alternative_data_type.setter |
570
|
|
|
def alternative_data_type(self, alt_data_type): |
571
|
|
|
if isinstance(alt_data_type, str) or alt_data_type is None: |
572
|
|
|
self.set_alternative_data_type(alt_data_type) |
573
|
|
|
return |
574
|
|
|
|
575
|
|
|
@property |
576
|
|
|
def analysis_methods(self): |
577
|
|
|
return self._pulseanalyzer.analysis_methods |
578
|
|
|
|
579
|
|
|
@property |
580
|
|
|
def extraction_methods(self): |
581
|
|
|
return self._pulseextractor.extraction_methods |
582
|
|
|
|
583
|
|
|
@property |
584
|
|
|
def analysis_settings(self): |
585
|
|
|
return self._pulseanalyzer.analysis_settings |
586
|
|
|
|
587
|
|
|
@analysis_settings.setter |
588
|
|
|
def analysis_settings(self, settings_dict): |
589
|
|
|
if isinstance(settings_dict, dict): |
590
|
|
|
self.set_analysis_settings(settings_dict) |
591
|
|
|
return |
592
|
|
|
|
593
|
|
|
@property |
594
|
|
|
def extraction_settings(self): |
595
|
|
|
return self._pulseextractor.extraction_settings |
596
|
|
|
|
597
|
|
|
@extraction_settings.setter |
598
|
|
|
def extraction_settings(self, settings_dict): |
599
|
|
|
if isinstance(settings_dict, dict): |
600
|
|
|
self.set_extraction_settings(settings_dict) |
601
|
|
|
return |
602
|
|
|
|
603
|
|
|
@QtCore.Slot(dict) |
604
|
|
|
def set_analysis_settings(self, settings_dict=None, **kwargs): |
605
|
|
|
""" |
606
|
|
|
Apply new analysis settings. |
607
|
|
|
Either accept a settings dictionary as positional argument or keyword arguments. |
608
|
|
|
If both are present both are being used by updating the settings_dict with kwargs. |
609
|
|
|
The keyword arguments take precedence over the items in settings_dict if there are |
610
|
|
|
conflicting names. |
611
|
|
|
|
612
|
|
|
@param settings_dict: |
613
|
|
|
@param kwargs: |
614
|
|
|
@return: |
615
|
|
|
""" |
616
|
|
|
# Determine complete settings dictionary |
617
|
|
|
if not isinstance(settings_dict, dict): |
618
|
|
|
settings_dict = kwargs |
619
|
|
|
else: |
620
|
|
|
settings_dict.update(kwargs) |
621
|
|
|
|
622
|
|
|
# Use threadlock to update settings during a running measurement |
623
|
|
|
with self._threadlock: |
624
|
|
|
self._pulseanalyzer.analysis_settings = settings_dict |
625
|
|
|
self.sigAnalysisSettingsUpdated.emit(self.analysis_settings) |
626
|
|
|
return |
627
|
|
|
|
628
|
|
|
@QtCore.Slot(dict) |
629
|
|
|
def set_extraction_settings(self, settings_dict=None, **kwargs): |
630
|
|
|
""" |
631
|
|
|
Apply new analysis settings. |
632
|
|
|
Either accept a settings dictionary as positional argument or keyword arguments. |
633
|
|
|
If both are present both are being used by updating the settings_dict with kwargs. |
634
|
|
|
The keyword arguments take precedence over the items in settings_dict if there are |
635
|
|
|
conflicting names. |
636
|
|
|
|
637
|
|
|
@param settings_dict: |
638
|
|
|
@param kwargs: |
639
|
|
|
@return: |
640
|
|
|
""" |
641
|
|
|
# Determine complete settings dictionary |
642
|
|
|
if not isinstance(settings_dict, dict): |
643
|
|
|
settings_dict = kwargs |
644
|
|
|
else: |
645
|
|
|
settings_dict.update(kwargs) |
646
|
|
|
|
647
|
|
|
# Use threadlock to update settings during a running measurement |
648
|
|
|
with self._threadlock: |
649
|
|
|
self._pulseextractor.extraction_settings = settings_dict |
650
|
|
|
self.sigExtractionSettingsUpdated.emit(self.extraction_settings) |
651
|
|
|
return |
652
|
|
|
|
653
|
|
|
@QtCore.Slot(dict) |
654
|
|
|
def set_measurement_settings(self, settings_dict=None, **kwargs): |
655
|
|
|
""" |
656
|
|
|
Apply new measurement settings. |
657
|
|
|
Either accept a settings dictionary as positional argument or keyword arguments. |
658
|
|
|
If both are present both are being used by updating the settings_dict with kwargs. |
659
|
|
|
The keyword arguments take precedence over the items in settings_dict if there are |
660
|
|
|
conflicting names. |
661
|
|
|
|
662
|
|
|
@param settings_dict: |
663
|
|
|
@param kwargs: |
664
|
|
|
@return: |
665
|
|
|
""" |
666
|
|
|
# Determine complete settings dictionary |
667
|
|
|
if not isinstance(settings_dict, dict): |
668
|
|
|
settings_dict = kwargs |
669
|
|
|
else: |
670
|
|
|
settings_dict.update(kwargs) |
671
|
|
|
|
672
|
|
|
# Check if invoke_settings flag has changed |
673
|
|
|
if 'invoke_settings' in settings_dict: |
674
|
|
|
self._invoke_settings_from_sequence = bool(settings_dict.get('invoke_settings')) |
675
|
|
|
|
676
|
|
|
# Invoke settings if measurement_information is present and flag is set |
677
|
|
|
if self._invoke_settings_from_sequence: |
678
|
|
|
if self._measurement_information: |
679
|
|
|
self._apply_invoked_settings() |
680
|
|
|
else: |
681
|
|
|
# Apply settings that can be changed while a measurement is running |
682
|
|
|
with self._threadlock: |
683
|
|
|
if 'units' in settings_dict: |
684
|
|
|
self._data_units = settings_dict.get('units') |
685
|
|
|
|
686
|
|
|
if self.module_state() == 'idle': |
687
|
|
|
# Get all other parameters if present |
688
|
|
|
if 'controlled_variable' in settings_dict: |
689
|
|
|
self._controlled_variable = np.array(settings_dict.get('controlled_variable'), |
690
|
|
|
dtype=float) |
691
|
|
|
if 'number_of_lasers' in settings_dict: |
692
|
|
|
self._number_of_lasers = int(settings_dict.get('number_of_lasers')) |
693
|
|
|
if self.fastcounter().is_gated(): |
694
|
|
|
self.set_fast_counter_settings(number_of_gates=self._number_of_lasers) |
695
|
|
|
if 'laser_ignore_list' in settings_dict: |
696
|
|
|
self._laser_ignore_list = sorted(settings_dict.get('laser_ignore_list')) |
697
|
|
|
if 'alternating' in settings_dict: |
698
|
|
|
self._alternating = bool(settings_dict.get('alternating')) |
699
|
|
|
|
700
|
|
|
# Perform sanity checks on settings |
701
|
|
|
self._measurement_settings_sanity_check() |
702
|
|
|
|
703
|
|
|
# emit update signal for master (GUI or other logic module) |
704
|
|
|
self.sigMeasurementSettingsUpdated.emit(self.measurement_settings) |
705
|
|
|
return self.measurement_settings |
706
|
|
|
|
707
|
|
|
@QtCore.Slot(bool, str) |
708
|
|
|
def toggle_pulsed_measurement(self, start, stash_raw_data_tag=''): |
709
|
|
|
""" |
710
|
|
|
Convenience method to start/stop measurement |
711
|
|
|
|
712
|
|
|
@param bool start: Start the measurement (True) or stop the measurement (False) |
713
|
|
|
""" |
714
|
|
|
if start: |
715
|
|
|
self.start_pulsed_measurement(stash_raw_data_tag) |
716
|
|
|
else: |
717
|
|
|
self.stop_pulsed_measurement(stash_raw_data_tag) |
718
|
|
|
return |
719
|
|
|
|
720
|
|
|
@QtCore.Slot(str) |
721
|
|
|
def start_pulsed_measurement(self, stashed_raw_data_tag=''): |
722
|
|
|
"""Start the analysis loop.""" |
723
|
|
|
self.sigMeasurementStatusUpdated.emit(True, False) |
724
|
|
|
|
725
|
|
|
# Check if measurement settings need to be invoked |
726
|
|
|
if self._invoke_settings_from_sequence: |
727
|
|
|
if self._measurement_information: |
728
|
|
|
self._apply_invoked_settings() |
729
|
|
|
self.sigMeasurementSettingsUpdated.emit(self.measurement_settings) |
730
|
|
|
else: |
731
|
|
|
# abort measurement if settings could not be invoked |
732
|
|
|
self.log.error('Unable to invoke measurement settings.\nThis feature can only be ' |
733
|
|
|
'used when creating the pulse sequence via predefined methods.\n' |
734
|
|
|
'Aborting measurement start.') |
735
|
|
|
self.set_measurement_settings(invoke_settings=False) |
736
|
|
|
self.sigMeasurementStatusUpdated.emit(False, False) |
737
|
|
|
return |
738
|
|
|
|
739
|
|
|
with self._threadlock: |
740
|
|
|
if self.module_state() == 'idle': |
741
|
|
|
# Lock module state |
742
|
|
|
self.module_state.lock() |
743
|
|
|
|
744
|
|
|
# Clear previous fits |
745
|
|
|
self.fc.clear_result() |
746
|
|
|
|
747
|
|
|
# initialize data arrays |
748
|
|
|
self._initialize_data_arrays() |
749
|
|
|
|
750
|
|
|
# recall stashed raw data |
751
|
|
|
if stashed_raw_data_tag in self._saved_raw_data: |
752
|
|
|
self._recalled_raw_data_tag = stashed_raw_data_tag |
753
|
|
|
self.log.info('Starting pulsed measurement with stashed raw data "{0}".' |
754
|
|
|
''.format(stashed_raw_data_tag)) |
755
|
|
|
else: |
756
|
|
|
self._recalled_raw_data_tag = None |
757
|
|
|
|
758
|
|
|
# start microwave source |
759
|
|
|
if self.__use_ext_microwave: |
760
|
|
|
self.microwave_on() |
761
|
|
|
# start fast counter |
762
|
|
|
self.fast_counter_on() |
763
|
|
|
# start pulse generator |
764
|
|
|
self.pulse_generator_on() |
765
|
|
|
|
766
|
|
|
# initialize analysis_timer |
767
|
|
|
self.__elapsed_time = 0.0 |
768
|
|
|
self.sigTimerUpdated.emit(self.__elapsed_time, |
769
|
|
|
self.__elapsed_sweeps, |
770
|
|
|
self.__timer_interval) |
771
|
|
|
|
772
|
|
|
# Set starting time and start timer (if present) |
773
|
|
|
self.__start_time = time.time() |
774
|
|
|
self.sigStartTimer.emit() |
775
|
|
|
|
776
|
|
|
# Set measurement paused flag |
777
|
|
|
self.__is_paused = False |
778
|
|
|
else: |
779
|
|
|
self.log.warning('Unable to start pulsed measurement. Measurement already running.') |
780
|
|
|
return |
781
|
|
|
|
782
|
|
|
@QtCore.Slot(str) |
783
|
|
|
def stop_pulsed_measurement(self, stash_raw_data_tag=''): |
784
|
|
|
""" |
785
|
|
|
Stop the measurement |
786
|
|
|
""" |
787
|
|
|
# Get raw data and analyze it a last time just before stopping the measurement. |
788
|
|
|
try: |
789
|
|
|
self._pulsed_analysis_loop() |
790
|
|
|
except: |
791
|
|
|
pass |
792
|
|
|
|
793
|
|
|
with self._threadlock: |
794
|
|
|
if self.module_state() == 'locked': |
795
|
|
|
# stopping the timer |
796
|
|
|
self.sigStopTimer.emit() |
797
|
|
|
# Turn off fast counter |
798
|
|
|
self.fast_counter_off() |
799
|
|
|
# Turn off pulse generator |
800
|
|
|
self.pulse_generator_off() |
801
|
|
|
# Turn off microwave source |
802
|
|
|
if self.__use_ext_microwave: |
803
|
|
|
self.microwave_off() |
804
|
|
|
|
805
|
|
|
# stash raw data if requested |
806
|
|
|
if stash_raw_data_tag: |
807
|
|
|
self.log.info('Raw data saved with tag "{0}" to continue measurement at a ' |
808
|
|
|
'later point.') |
809
|
|
|
self._saved_raw_data[stash_raw_data_tag] = self.raw_data.copy() |
810
|
|
|
self._recalled_raw_data_tag = None |
811
|
|
|
|
812
|
|
|
# Set measurement paused flag |
813
|
|
|
self.__is_paused = False |
814
|
|
|
|
815
|
|
|
self.module_state.unlock() |
816
|
|
|
self.sigMeasurementStatusUpdated.emit(False, False) |
817
|
|
|
return |
818
|
|
|
|
819
|
|
|
@QtCore.Slot(bool) |
820
|
|
|
def toggle_measurement_pause(self, pause): |
821
|
|
|
""" |
822
|
|
|
Convenience method to pause/continue measurement |
823
|
|
|
|
824
|
|
|
@param bool pause: Pause the measurement (True) or continue the measurement (False) |
825
|
|
|
""" |
826
|
|
|
if pause: |
827
|
|
|
self.pause_pulsed_measurement() |
828
|
|
|
else: |
829
|
|
|
self.continue_pulsed_measurement() |
830
|
|
View Code Duplication |
return |
|
|
|
|
831
|
|
|
|
832
|
|
|
@QtCore.Slot() |
833
|
|
|
def pause_pulsed_measurement(self): |
834
|
|
|
""" |
835
|
|
|
Pauses the measurement |
836
|
|
|
""" |
837
|
|
|
with self._threadlock: |
838
|
|
|
if self.module_state() == 'locked': |
839
|
|
|
# pausing the timer |
840
|
|
|
if self.__analysis_timer.isActive(): |
841
|
|
|
# stopping the timer |
842
|
|
|
self.sigStopTimer.emit() |
843
|
|
|
|
844
|
|
|
self.fast_counter_pause() |
845
|
|
|
self.pulse_generator_off() |
846
|
|
|
if self.__use_ext_microwave: |
847
|
|
|
self.microwave_off() |
848
|
|
|
|
849
|
|
|
# Set measurement paused flag |
850
|
|
|
self.__is_paused = True |
851
|
|
|
|
852
|
|
|
self.sigMeasurementStatusUpdated.emit(True, True) |
853
|
|
|
else: |
854
|
|
|
self.log.warning('Unable to pause pulsed measurement. No measurement running.') |
855
|
|
|
self.sigMeasurementStatusUpdated.emit(False, False) |
856
|
|
View Code Duplication |
return |
|
|
|
|
857
|
|
|
|
858
|
|
|
@QtCore.Slot() |
859
|
|
|
def continue_pulsed_measurement(self): |
860
|
|
|
""" |
861
|
|
|
Continues the measurement |
862
|
|
|
""" |
863
|
|
|
with self._threadlock: |
864
|
|
|
if self.module_state() == 'locked': |
865
|
|
|
if self.__use_ext_microwave: |
866
|
|
|
self.microwave_on() |
867
|
|
|
self.fast_counter_continue() |
868
|
|
|
self.pulse_generator_on() |
869
|
|
|
|
870
|
|
|
# un-pausing the timer |
871
|
|
|
if not self.__analysis_timer.isActive(): |
872
|
|
|
self.sigStartTimer.emit() |
873
|
|
|
|
874
|
|
|
# Set measurement paused flag |
875
|
|
|
self.__is_paused = False |
876
|
|
|
|
877
|
|
|
self.sigMeasurementStatusUpdated.emit(True, False) |
878
|
|
|
else: |
879
|
|
|
self.log.warning('Unable to continue pulsed measurement. No measurement running.') |
880
|
|
|
self.sigMeasurementStatusUpdated.emit(False, False) |
881
|
|
|
return |
882
|
|
|
|
883
|
|
|
@QtCore.Slot(float) |
884
|
|
|
@QtCore.Slot(int) |
885
|
|
|
def set_timer_interval(self, interval): |
886
|
|
|
""" |
887
|
|
|
Change the interval of the measurement analysis timer |
888
|
|
|
|
889
|
|
|
@param int|float interval: Interval of the timer in s |
890
|
|
|
""" |
891
|
|
|
with self._threadlock: |
892
|
|
|
self.__timer_interval = interval |
893
|
|
|
if self.__timer_interval > 0: |
894
|
|
|
self.__analysis_timer.setInterval(int(1000. * self.__timer_interval)) |
895
|
|
|
if self.module_state() == 'locked' and not self.__is_paused: |
896
|
|
|
self.sigStartTimer.emit() |
897
|
|
|
else: |
898
|
|
|
self.sigStopTimer.emit() |
899
|
|
|
|
900
|
|
|
self.sigTimerUpdated.emit(self.__elapsed_time, self.__elapsed_sweeps, |
901
|
|
|
self.__timer_interval) |
902
|
|
|
return |
903
|
|
|
|
904
|
|
|
@QtCore.Slot(str) |
905
|
|
|
def set_alternative_data_type(self, alt_data_type): |
906
|
|
|
""" |
907
|
|
|
|
908
|
|
|
@param alt_data_type: |
909
|
|
|
@return: |
910
|
|
|
""" |
911
|
|
|
with self._threadlock: |
912
|
|
|
if alt_data_type == 'Delta' and not self._alternating: |
913
|
|
|
if self._alternative_data_type == 'Delta': |
914
|
|
|
self._alternative_data_type = None |
915
|
|
|
self.log.error('Can not set "Delta" as alternative data calculation if measurement is ' |
916
|
|
|
'not alternating.\n' |
917
|
|
|
'Setting to previous type "{0}".'.format(self.alternative_data_type)) |
918
|
|
|
else: |
919
|
|
|
self._alternative_data_type = alt_data_type |
920
|
|
|
|
921
|
|
|
self._compute_alt_data() |
922
|
|
|
self.sigMeasurementDataUpdated.emit() |
923
|
|
|
return |
924
|
|
|
|
925
|
|
|
@QtCore.Slot() |
926
|
|
|
def manually_pull_data(self): |
927
|
|
|
""" Analyse and display the data |
928
|
|
|
""" |
929
|
|
|
if self.module_state() == 'locked': |
930
|
|
|
self._pulsed_analysis_loop() |
931
|
|
|
return |
932
|
|
|
|
933
|
|
|
@QtCore.Slot(str) |
934
|
|
|
@QtCore.Slot(str, np.ndarray) |
935
|
|
|
def do_fit(self, fit_method, data=None): |
936
|
|
|
""" |
937
|
|
|
Performs the chosen fit on the measured data. |
938
|
|
|
|
939
|
|
|
@param str fit_method: name of the fit method to use |
940
|
|
|
@param 2D numpy.ndarray data: the x and y data points for the fit (shape=(2,X)) |
941
|
|
|
|
942
|
|
|
@return (2D numpy.ndarray, result object): the resulting fit data and the fit result object |
943
|
|
|
""" |
944
|
|
|
# Set current fit |
945
|
|
|
self.fc.set_current_fit(fit_method) |
946
|
|
|
|
947
|
|
|
if data is None: |
948
|
|
|
data = self.signal_data |
949
|
|
|
update_fit_data = True |
950
|
|
|
else: |
951
|
|
|
update_fit_data = False |
952
|
|
|
|
953
|
|
|
x_fit, y_fit, result = self.fc.do_fit(data[0], data[1]) |
954
|
|
|
|
955
|
|
|
fit_data = np.array([x_fit, y_fit]) |
956
|
|
|
fit_name = self.fc.current_fit |
957
|
|
|
fit_result = self.fc.current_fit_result |
958
|
|
|
|
959
|
|
|
if update_fit_data: |
960
|
|
|
self.signal_fit_data = fit_data |
961
|
|
|
self.sigFitUpdated.emit(fit_name, self.signal_fit_data, fit_result) |
962
|
|
|
|
963
|
|
|
return fit_data, fit_result |
964
|
|
|
|
965
|
|
|
def _apply_invoked_settings(self): |
966
|
|
|
""" |
967
|
|
|
""" |
968
|
|
|
if not isinstance(self._measurement_information, dict) or not self._measurement_information: |
969
|
|
|
self.log.warning('Can\'t invoke measurement settings from sequence information ' |
970
|
|
|
'since no measurement_information container is given.') |
971
|
|
|
return |
972
|
|
|
|
973
|
|
|
# First try to set parameters that can be changed during a running measurement |
974
|
|
|
if 'units' in self._measurement_information: |
975
|
|
|
with self._threadlock: |
976
|
|
|
self._data_units = self._measurement_information.get('units') |
977
|
|
|
|
978
|
|
|
# Check if a measurement is running and apply following settings if this is not the case |
979
|
|
|
if self.module_state() == 'locked': |
980
|
|
|
return |
981
|
|
|
|
982
|
|
|
if 'number_of_lasers' in self._measurement_information: |
983
|
|
|
self._number_of_lasers = int(self._measurement_information.get('number_of_lasers')) |
984
|
|
|
else: |
985
|
|
|
self.log.error('Unable to invoke setting for "number_of_lasers".\n' |
986
|
|
|
'Measurement information container is incomplete/invalid.') |
987
|
|
|
return |
988
|
|
|
|
989
|
|
|
if 'laser_ignore_list' in self._measurement_information: |
990
|
|
|
self._laser_ignore_list = sorted(self._measurement_information.get('laser_ignore_list')) |
991
|
|
|
else: |
992
|
|
|
self.log.error('Unable to invoke setting for "laser_ignore_list".\n' |
993
|
|
|
'Measurement information container is incomplete/invalid.') |
994
|
|
|
return |
995
|
|
|
|
996
|
|
|
if 'alternating' in self._measurement_information: |
997
|
|
|
self._alternating = bool(self._measurement_information.get('alternating')) |
998
|
|
|
else: |
999
|
|
|
self.log.error('Unable to invoke setting for "alternating".\n' |
1000
|
|
|
'Measurement information container is incomplete/invalid.') |
1001
|
|
|
return |
1002
|
|
|
|
1003
|
|
|
if 'controlled_variable' in self._measurement_information: |
1004
|
|
|
self._controlled_variable = np.array( |
1005
|
|
|
self._measurement_information.get('controlled_variable'), dtype=float) |
1006
|
|
|
else: |
1007
|
|
|
self.log.error('Unable to invoke setting for "controlled_variable".\n' |
1008
|
|
|
'Measurement information container is incomplete/invalid.') |
1009
|
|
|
return |
1010
|
|
|
|
1011
|
|
|
if 'counting_length' in self._measurement_information: |
1012
|
|
|
fast_counter_record_length = self._measurement_information.get('counting_length') |
1013
|
|
|
else: |
1014
|
|
|
self.log.error('Unable to invoke setting for "counting_length".\n' |
1015
|
|
|
'Measurement information container is incomplete/invalid.') |
1016
|
|
|
return |
1017
|
|
|
|
1018
|
|
|
if self.fastcounter().is_gated(): |
1019
|
|
|
self.set_fast_counter_settings(number_of_gates=self._number_of_lasers, |
1020
|
|
|
record_length=fast_counter_record_length) |
1021
|
|
|
else: |
1022
|
|
|
self.set_fast_counter_settings(record_length=fast_counter_record_length) |
1023
|
|
|
return |
1024
|
|
|
|
1025
|
|
|
def _measurement_settings_sanity_check(self): |
1026
|
|
|
number_of_analyzed_lasers = self._number_of_lasers - len(self._laser_ignore_list) |
1027
|
|
|
if len(self._controlled_variable) < 1: |
1028
|
|
|
self.log.error('Tried to set empty controlled variables array. This can not work.') |
1029
|
|
|
|
1030
|
|
|
if self._alternating and (number_of_analyzed_lasers // 2) != len(self._controlled_variable): |
1031
|
|
|
self.log.error('Half of the number of laser pulses to analyze ({0}) does not match the ' |
1032
|
|
|
'number of controlled_variable ticks ({1:d}).' |
1033
|
|
|
''.format(number_of_analyzed_lasers // 2, |
1034
|
|
|
len(self._controlled_variable))) |
1035
|
|
|
elif not self._alternating and number_of_analyzed_lasers != len(self._controlled_variable): |
1036
|
|
|
self.log.error('Number of laser pulses to analyze ({0:d}) does not match the number of ' |
1037
|
|
|
'controlled_variable ticks ({1:d}).' |
1038
|
|
|
''.format(number_of_analyzed_lasers, len(self._controlled_variable))) |
1039
|
|
|
|
1040
|
|
|
if self.fastcounter().is_gated() and self._number_of_lasers != self.__fast_counter_gates: |
1041
|
|
|
self.log.error('Gated fast counter gate number differs from number of laser pulses ' |
1042
|
|
|
'configured in measurement settings.') |
1043
|
|
|
return |
1044
|
|
|
|
1045
|
|
|
def _pulsed_analysis_loop(self): |
1046
|
|
|
""" Acquires laser pulses from fast counter, |
1047
|
|
|
calculates fluorescence signal and creates plots. |
1048
|
|
|
""" |
1049
|
|
|
with self._threadlock: |
1050
|
|
|
if self.module_state() == 'locked': |
1051
|
|
|
# Update elapsed time |
1052
|
|
|
self.__elapsed_time = time.time() - self.__start_time |
1053
|
|
|
|
1054
|
|
|
# Get counter raw data (including recalled raw data from previous measurement) |
1055
|
|
|
self.raw_data = self._get_raw_data() |
1056
|
|
|
|
1057
|
|
|
# extract laser pulses from raw data |
1058
|
|
|
return_dict = self._pulseextractor.extract_laser_pulses(self.raw_data) |
1059
|
|
|
self.laser_data = return_dict['laser_counts_arr'] |
1060
|
|
|
|
1061
|
|
|
# analyze pulses and get data points for signal array. Also check if extraction |
1062
|
|
|
# worked (non-zero array returned). |
1063
|
|
|
if self.laser_data.any(): |
1064
|
|
|
tmp_signal, tmp_error = self._pulseanalyzer.analyse_laser_pulses( |
1065
|
|
|
self.laser_data) |
1066
|
|
|
else: |
1067
|
|
|
tmp_signal = np.zeros(self.laser_data.shape[0]) |
1068
|
|
|
tmp_error = np.zeros(self.laser_data.shape[0]) |
1069
|
|
|
|
1070
|
|
|
# exclude laser pulses to ignore |
1071
|
|
|
if len(self._laser_ignore_list) > 0: |
1072
|
|
|
# Convert relative negative indices into absolute positive indices |
1073
|
|
|
while self._laser_ignore_list[0] < 0: |
1074
|
|
|
neg_index = self._laser_ignore_list[0] |
1075
|
|
|
self._laser_ignore_list[0] = len(tmp_signal) + neg_index |
1076
|
|
|
self._laser_ignore_list.sort() |
1077
|
|
|
|
1078
|
|
|
tmp_signal = np.delete(tmp_signal, self._laser_ignore_list) |
1079
|
|
|
tmp_error = np.delete(tmp_error, self._laser_ignore_list) |
1080
|
|
|
|
1081
|
|
|
# order data according to alternating flag |
1082
|
|
|
if self._alternating: |
1083
|
|
|
self.signal_data[1] = tmp_signal[::2] |
1084
|
|
|
self.signal_data[2] = tmp_signal[1::2] |
1085
|
|
|
self.measurement_error[1] = tmp_error[::2] |
1086
|
|
|
self.measurement_error[2] = tmp_error[1::2] |
1087
|
|
|
else: |
1088
|
|
|
self.signal_data[1] = tmp_signal |
1089
|
|
|
self.measurement_error[1] = tmp_error |
1090
|
|
|
|
1091
|
|
|
# Compute alternative data array from signal |
1092
|
|
|
self._compute_alt_data() |
1093
|
|
|
|
1094
|
|
|
# emit signals |
1095
|
|
|
self.sigTimerUpdated.emit(self.__elapsed_time, self.__elapsed_sweeps, |
1096
|
|
|
self.__timer_interval) |
1097
|
|
|
self.sigMeasurementDataUpdated.emit() |
1098
|
|
|
return |
1099
|
|
|
|
1100
|
|
|
def _get_raw_data(self): |
1101
|
|
|
""" |
1102
|
|
|
Get the raw count data from the fast counting hardware and perform sanity checks. |
1103
|
|
|
Also add recalled raw data to the newly received data. |
1104
|
|
|
:return numpy.ndarray: The count data (1D for ungated, 2D for gated counter) |
1105
|
|
|
""" |
1106
|
|
|
# get raw data from fast counter |
1107
|
|
|
fc_data = netobtain(self.fastcounter().get_data_trace()) |
1108
|
|
|
|
1109
|
|
|
# add old raw data from previous measurements if necessary |
1110
|
|
|
if self._saved_raw_data.get(self._recalled_raw_data_tag) is not None: |
1111
|
|
|
self.log.info('Found old saved raw data with tag "{0}".' |
1112
|
|
|
''.format(self._recalled_raw_data_tag)) |
1113
|
|
|
if not fc_data.any(): |
1114
|
|
|
self.log.warning('Only zeros received from fast counter!\n' |
1115
|
|
|
'Using recalled raw data only.') |
1116
|
|
|
fc_data = self._saved_raw_data[self._recalled_raw_data_tag] |
1117
|
|
|
elif self._saved_raw_data[self._recalled_raw_data_tag].shape == fc_data.shape: |
1118
|
|
|
self.log.debug('Recalled raw data has the same shape as current data.') |
1119
|
|
|
fc_data = self._saved_raw_data[self._recalled_raw_data_tag] + fc_data |
1120
|
|
|
else: |
1121
|
|
|
self.log.warning('Recalled raw data has not the same shape as current data.' |
1122
|
|
|
'\nDid NOT add recalled raw data to current time trace.') |
1123
|
|
|
elif not fc_data.any(): |
1124
|
|
|
self.log.warning('Only zeros received from fast counter!') |
1125
|
|
|
fc_data = np.zeros(fc_data.shape, dtype='int64') |
1126
|
|
|
return fc_data |
1127
|
|
|
|
1128
|
|
|
def _initialize_data_arrays(self): |
1129
|
|
|
""" |
1130
|
|
|
Initializing the signal, error, laser and raw data arrays. |
1131
|
|
|
""" |
1132
|
|
|
# Determine signal array dimensions |
1133
|
|
|
signal_dim = 3 if self._alternating else 2 |
1134
|
|
|
|
1135
|
|
|
self.signal_data = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
1136
|
|
|
self.signal_data[0] = self._controlled_variable |
1137
|
|
|
|
1138
|
|
|
self.signal_alt_data = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
1139
|
|
|
self.signal_alt_data[0] = self._controlled_variable |
1140
|
|
|
|
1141
|
|
|
self.measurement_error = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
1142
|
|
|
self.measurement_error[0] = self._controlled_variable |
1143
|
|
|
|
1144
|
|
|
number_of_bins = int(self.__fast_counter_record_length / self.__fast_counter_binwidth) |
1145
|
|
|
laser_length = number_of_bins if self.__fast_counter_gates > 0 else 500 |
1146
|
|
|
self.laser_data = np.zeros((self._number_of_lasers, laser_length), dtype='int64') |
1147
|
|
|
|
1148
|
|
|
if self.__fast_counter_gates > 0: |
1149
|
|
|
self.raw_data = np.zeros((self._number_of_lasers, number_of_bins), dtype='int64') |
1150
|
|
|
else: |
1151
|
|
|
self.raw_data = np.zeros(number_of_bins, dtype='int64') |
1152
|
|
|
|
1153
|
|
|
self.sigMeasurementDataUpdated.emit() |
1154
|
|
|
return |
1155
|
|
|
|
1156
|
|
|
# FIXME: Revise everything below |
1157
|
|
|
|
1158
|
|
|
############################################################################ |
1159
|
|
|
@QtCore.Slot(str, bool) |
1160
|
|
|
def save_measurement_data(self, tag=None, with_error=True): |
1161
|
|
|
""" |
1162
|
|
|
Prepare data to be saved and create a proper plot of the data |
1163
|
|
|
|
1164
|
|
|
@param str tag: a filetag which will be included in the filename |
1165
|
|
|
@param bool with_error: select whether errors should be saved/plotted |
1166
|
|
|
|
1167
|
|
|
@return str: filepath where data were saved |
1168
|
|
|
""" |
1169
|
|
|
filepath = self.savelogic().get_path_for_module('PulsedMeasurement') |
1170
|
|
|
timestamp = datetime.datetime.now() |
1171
|
|
|
|
1172
|
|
|
##################################################################### |
1173
|
|
|
#### Save extracted laser pulses #### |
1174
|
|
|
##################################################################### |
1175
|
|
|
if tag is not None and len(tag) > 0: |
1176
|
|
|
filelabel = tag + '_laser_pulses' |
1177
|
|
|
else: |
1178
|
|
|
filelabel = 'laser_pulses' |
1179
|
|
|
|
1180
|
|
|
# prepare the data in a dict or in an OrderedDict: |
1181
|
|
|
data = OrderedDict() |
1182
|
|
|
laser_trace = self.laser_data |
1183
|
|
|
data['Signal (counts)'.format()] = laser_trace.transpose() |
1184
|
|
|
|
1185
|
|
|
# write the parameters: |
1186
|
|
|
parameters = OrderedDict() |
1187
|
|
|
parameters['bin width (s)'] = self.__fast_counter_binwidth |
1188
|
|
|
parameters['record length (s)'] = self.__fast_counter_record_length |
1189
|
|
|
parameters['gated counting'] = self.fast_counter_settings['is_gated'] |
1190
|
|
|
parameters['extraction parameters'] = self.extraction_settings |
1191
|
|
|
|
1192
|
|
|
self.savelogic().save_data(data, |
1193
|
|
|
timestamp=timestamp, |
1194
|
|
|
parameters=parameters, |
1195
|
|
|
filepath=filepath, |
1196
|
|
|
filelabel=filelabel, |
1197
|
|
|
filetype='text', |
1198
|
|
|
fmt='%d', |
1199
|
|
|
delimiter='\t') |
1200
|
|
|
|
1201
|
|
|
##################################################################### |
1202
|
|
|
#### Save measurement data #### |
1203
|
|
|
##################################################################### |
1204
|
|
|
if tag is not None and len(tag) > 0: |
1205
|
|
|
filelabel = tag + '_pulsed_measurement' |
1206
|
|
|
else: |
1207
|
|
|
filelabel = 'pulsed_measurement' |
1208
|
|
|
|
1209
|
|
|
# prepare the data in a dict or in an OrderedDict: |
1210
|
|
|
header_str = 'Controlled variable({0})\tSignal({1})\t'.format(*self._data_units) |
1211
|
|
|
if self._alternating: |
1212
|
|
|
header_str += 'Signal2({0})'.format(self._data_units[1]) |
1213
|
|
|
if with_error: |
1214
|
|
|
header_str += 'Error({0})'.format(self._data_units[1]) |
1215
|
|
|
if self._alternating: |
1216
|
|
|
header_str += 'Error2({0})'.format(self._data_units[1]) |
1217
|
|
|
data = OrderedDict() |
1218
|
|
|
if with_error: |
1219
|
|
|
data[header_str] = np.vstack((self.signal_data, self.measurement_error)).transpose() |
1220
|
|
|
else: |
1221
|
|
|
data[header_str] = self.signal_data.transpose() |
1222
|
|
|
|
1223
|
|
|
# write the parameters: |
1224
|
|
|
parameters = OrderedDict() |
1225
|
|
|
parameters['Approx. measurement time (s)'] = self.__elapsed_time |
1226
|
|
|
parameters['Measurement sweeps'] = self.__elapsed_sweeps |
1227
|
|
|
parameters['Number of laser pulses'] = self._number_of_lasers |
1228
|
|
|
parameters['Laser ignore indices'] = self._laser_ignore_list |
1229
|
|
|
parameters['alternating'] = self._alternating |
1230
|
|
|
parameters['analysis parameters'] = self.analysis_settings |
1231
|
|
|
parameters['extraction parameters'] = self.extraction_settings |
1232
|
|
|
parameters['fast counter settings'] = self.fast_counter_settings |
1233
|
|
|
|
1234
|
|
|
# Prepare the figure to save as a "data thumbnail" |
1235
|
|
|
plt.style.use(self.savelogic().mpl_qd_style) |
1236
|
|
|
|
1237
|
|
|
# extract the possible colors from the colorscheme: |
1238
|
|
|
prop_cycle = self.savelogic().mpl_qd_style['axes.prop_cycle'] |
1239
|
|
|
colors = {} |
1240
|
|
|
for i, color_setting in enumerate(prop_cycle): |
1241
|
|
|
colors[i] = color_setting['color'] |
1242
|
|
|
|
1243
|
|
|
# scale the x_axis for plotting |
1244
|
|
|
max_val = np.max(self.signal_data[0]) |
1245
|
|
|
scaled_float = units.ScaledFloat(max_val) |
1246
|
|
|
counts_prefix = scaled_float.scale |
1247
|
|
|
x_axis_scaled = self.signal_data[0] / scaled_float.scale_val |
1248
|
|
|
|
1249
|
|
|
# Create the figure object |
1250
|
|
|
if self._alternative_data_type: |
1251
|
|
|
fig, (ax1, ax2) = plt.subplots(2, 1) |
1252
|
|
|
else: |
1253
|
|
|
fig, ax1 = plt.subplots() |
1254
|
|
|
|
1255
|
|
|
if with_error: |
1256
|
|
|
ax1.errorbar(x=x_axis_scaled, y=self.signal_data[1], |
1257
|
|
|
yerr=self.measurement_error[1], fmt='-o', |
1258
|
|
|
linestyle=':', linewidth=0.5, color=colors[0], |
1259
|
|
|
ecolor=colors[1], capsize=3, capthick=0.9, |
1260
|
|
|
elinewidth=1.2, label='data trace 1') |
1261
|
|
|
|
1262
|
|
|
if self._alternating: |
1263
|
|
|
ax1.errorbar(x=x_axis_scaled, y=self.signal_data[2], |
1264
|
|
|
yerr=self.measurement_error[2], fmt='-D', |
1265
|
|
|
linestyle=':', linewidth=0.5, color=colors[3], |
1266
|
|
|
ecolor=colors[4], capsize=3, capthick=0.7, |
1267
|
|
|
elinewidth=1.2, label='data trace 2') |
1268
|
|
|
else: |
1269
|
|
|
ax1.plot(x_axis_scaled, self.signal_data[1], '-o', color=colors[0], |
1270
|
|
|
linestyle=':', linewidth=0.5, label='data trace 1') |
1271
|
|
|
|
1272
|
|
|
if self._alternating: |
1273
|
|
|
ax1.plot(x_axis_scaled, self.signal_data[2], '-o', |
1274
|
|
|
color=colors[3], linestyle=':', linewidth=0.5, |
1275
|
|
|
label='data trace 2') |
1276
|
|
|
|
1277
|
|
|
# Do not include fit curve if there is no fit calculated. |
1278
|
|
|
if self.signal_fit_data.size != 0 and np.max(self.signal_fit_data[1]) > 0: |
1279
|
|
|
x_axis_fit_scaled = self.signal_fit_data[0] / scaled_float.scale_val |
1280
|
|
|
ax1.plot(x_axis_fit_scaled, self.signal_fit_data[1], |
1281
|
|
|
color=colors[2], marker='None', linewidth=1.5, |
1282
|
|
|
label='fit: {0}'.format(self.fc.current_fit)) |
1283
|
|
|
|
1284
|
|
|
# add then the fit result to the plot: |
1285
|
|
|
|
1286
|
|
|
# Parameters for the text plot: |
1287
|
|
|
# The position of the text annotation is controlled with the |
1288
|
|
|
# relative offset in x direction and the relative length factor |
1289
|
|
|
# rel_len_fac of the longest entry in one column |
1290
|
|
|
rel_offset = 0.02 |
1291
|
|
|
rel_len_fac = 0.011 |
1292
|
|
|
entries_per_col = 24 |
1293
|
|
|
|
1294
|
|
|
# create the formatted fit text: |
1295
|
|
|
if hasattr(self.fc.current_fit_result, 'result_str_dict'): |
1296
|
|
|
fit_res = units.create_formatted_output(self.fc.current_fit_result.result_str_dict) |
1297
|
|
|
else: |
1298
|
|
|
self.log.warning('The fit container does not contain any data ' |
1299
|
|
|
'from the fit! Apply the fit once again.') |
1300
|
|
|
fit_res = '' |
1301
|
|
|
# do reverse processing to get each entry in a list |
1302
|
|
|
entry_list = fit_res.split('\n') |
1303
|
|
|
# slice the entry_list in entries_per_col |
1304
|
|
|
chunks = [entry_list[x:x+entries_per_col] for x in range(0, len(entry_list), entries_per_col)] |
1305
|
|
|
|
1306
|
|
|
is_first_column = True # first entry should contain header or \n |
1307
|
|
|
shift = rel_offset |
1308
|
|
|
|
1309
|
|
|
for column in chunks: |
1310
|
|
|
|
1311
|
|
|
max_length = max(column, key=len) # get the longest entry |
1312
|
|
|
column_text = '' |
1313
|
|
|
|
1314
|
|
|
for entry in column: |
1315
|
|
|
column_text += entry + '\n' |
1316
|
|
|
|
1317
|
|
|
column_text = column_text[:-1] # remove the last new line |
1318
|
|
|
|
1319
|
|
|
heading = '' |
1320
|
|
|
if is_first_column: |
1321
|
|
|
heading = 'Fit results:' |
1322
|
|
|
|
1323
|
|
|
column_text = heading + '\n' + column_text |
1324
|
|
|
|
1325
|
|
|
ax1.text(1.00 + shift, 0.99, column_text, |
1326
|
|
|
verticalalignment='top', |
1327
|
|
|
horizontalalignment='left', |
1328
|
|
|
transform=ax1.transAxes, |
1329
|
|
|
fontsize=12) |
1330
|
|
|
|
1331
|
|
|
# the shift in position of the text is a linear function |
1332
|
|
|
# which depends on the longest entry in the column |
1333
|
|
|
shift += rel_len_fac * len(max_length) |
1334
|
|
|
|
1335
|
|
|
is_first_column = False |
1336
|
|
|
|
1337
|
|
|
# handle the save of the alternative data plot |
1338
|
|
|
if self._alternative_data_type: |
1339
|
|
|
|
1340
|
|
|
# scale the x_axis for plotting |
1341
|
|
|
max_val = np.max(self.signal_alt_data[0]) |
1342
|
|
|
scaled_float = units.ScaledFloat(max_val) |
1343
|
|
|
x_axis_prefix = scaled_float.scale |
1344
|
|
|
x_axis_ft_scaled = self.signal_alt_data[0] / scaled_float.scale_val |
1345
|
|
|
|
1346
|
|
|
# since no ft units are provided, make a small work around: |
1347
|
|
|
if self._alternative_data_type == 'FFT': |
1348
|
|
|
if self._data_units[0] == 's': |
1349
|
|
|
inverse_cont_var = 'Hz' |
1350
|
|
|
elif self._data_units[0] == 'Hz': |
1351
|
|
|
inverse_cont_var = 's' |
1352
|
|
|
else: |
1353
|
|
|
inverse_cont_var = '(1/{0})'.format(self._data_units[0]) |
1354
|
|
|
x_axis_ft_label = 'Fourier Transformed controlled variable (' + x_axis_prefix + inverse_cont_var + ')' |
1355
|
|
|
y_axis_ft_label = 'Fourier amplitude (arb. u.)' |
1356
|
|
|
ft_label = 'FT of data trace 1' |
1357
|
|
|
else: |
1358
|
|
|
x_axis_ft_label = 'controlled variable (' + self._data_units[0] + ')' |
1359
|
|
|
y_axis_ft_label = 'norm. sig (arb. u.)' |
1360
|
|
|
ft_label = '' |
1361
|
|
|
|
1362
|
|
|
ax2.plot(x_axis_ft_scaled, self.signal_alt_data[1], '-o', |
1363
|
|
|
linestyle=':', linewidth=0.5, color=colors[0], |
1364
|
|
|
label=ft_label) |
1365
|
|
|
|
1366
|
|
|
ax2.set_xlabel(x_axis_ft_label) |
1367
|
|
|
ax2.set_ylabel(y_axis_ft_label) |
1368
|
|
|
ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
1369
|
|
|
mode="expand", borderaxespad=0.) |
1370
|
|
|
|
1371
|
|
|
#FIXME: no fit plot for the alternating graph, use for that graph colors[5] |
1372
|
|
|
ax1.set_xlabel('controlled variable (' + counts_prefix + self._data_units[0] + ')') |
1373
|
|
|
ax1.set_ylabel('signal (' + self._data_units[1] + ')') |
1374
|
|
|
|
1375
|
|
|
fig.tight_layout() |
1376
|
|
|
ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
1377
|
|
|
mode="expand", borderaxespad=0.) |
1378
|
|
|
# plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
1379
|
|
|
# mode="expand", borderaxespad=0.) |
1380
|
|
|
|
1381
|
|
|
self.savelogic().save_data(data, timestamp=timestamp, |
1382
|
|
|
parameters=parameters, fmt='%.15e', |
1383
|
|
|
filepath=filepath, filelabel=filelabel, filetype='text', |
1384
|
|
|
delimiter='\t', plotfig=fig) |
1385
|
|
|
|
1386
|
|
|
##################################################################### |
1387
|
|
|
#### Save raw data timetrace #### |
1388
|
|
|
##################################################################### |
1389
|
|
|
filelabel = 'raw_timetrace' if not tag else tag + '_raw_timetrace' |
1390
|
|
|
|
1391
|
|
|
# prepare the data in a dict or in an OrderedDict: |
1392
|
|
|
data = OrderedDict() |
1393
|
|
|
raw_trace = self.raw_data.astype('int64') |
1394
|
|
|
data['Signal(counts)'] = raw_trace.transpose() |
1395
|
|
|
# write the parameters: |
1396
|
|
|
parameters = OrderedDict() |
1397
|
|
|
parameters['bin width (s)'] = self.__fast_counter_binwidth |
1398
|
|
|
parameters['record length (s)'] = self.__fast_counter_record_length |
1399
|
|
|
parameters['gated counting'] = self.fast_counter_settings['is_gated'] |
1400
|
|
|
parameters['Number of laser pulses'] = self._number_of_lasers |
1401
|
|
|
parameters['alternating'] = self._alternating |
1402
|
|
|
parameters['Controlled variable'] = list(self.signal_data[0]) |
1403
|
|
|
|
1404
|
|
|
self.savelogic().save_data(data, timestamp=timestamp, |
1405
|
|
|
parameters=parameters, fmt='%d', |
1406
|
|
|
filepath=filepath, filelabel=filelabel, |
1407
|
|
|
filetype=self._raw_data_save_type, |
1408
|
|
|
delimiter='\t') |
1409
|
|
|
return filepath |
1410
|
|
|
|
1411
|
|
|
def _compute_alt_data(self): |
1412
|
|
|
""" |
1413
|
|
|
Performing transformations on the measurement data (e.g. fourier transform). |
1414
|
|
|
""" |
1415
|
|
|
if self._alternative_data_type == 'Delta' and len(self.signal_data) == 3: |
1416
|
|
|
self.signal_alt_data = np.empty((2, self.signal_data.shape[1]), dtype=float) |
1417
|
|
|
self.signal_alt_data[0] = self.signal_data[0] |
1418
|
|
|
self.signal_alt_data[1] = self.signal_data[1] - self.signal_data[2] |
1419
|
|
|
elif self._alternative_data_type == 'FFT' and self.signal_data.shape[1] >= 2: |
1420
|
|
|
fft_x, fft_y = units.compute_ft(x_val=self.signal_data[0], |
1421
|
|
|
y_val=self.signal_data[1], |
1422
|
|
|
zeropad_num=self.zeropad, |
1423
|
|
|
window=self.window, |
1424
|
|
|
base_corr=self.base_corr, |
1425
|
|
|
psd=self.psd) |
1426
|
|
|
self.signal_alt_data = np.empty((len(self.signal_data), len(fft_x)), dtype=float) |
1427
|
|
|
self.signal_alt_data[0] = fft_x |
1428
|
|
|
self.signal_alt_data[1] = fft_y |
1429
|
|
|
for dim in range(2, len(self.signal_data)): |
1430
|
|
|
dummy, self.signal_alt_data[dim] = units.compute_ft(x_val=self.signal_data[0], |
1431
|
|
|
y_val=self.signal_data[dim], |
1432
|
|
|
zeropad_num=self.zeropad, |
1433
|
|
|
window=self.window, |
1434
|
|
|
base_corr=self.base_corr, |
1435
|
|
|
psd=self.psd) |
1436
|
|
|
else: |
1437
|
|
|
self.signal_alt_data = np.zeros(self.signal_data.shape, dtype=float) |
1438
|
|
|
self.signal_alt_data[0] = self.signal_data[0] |
1439
|
|
|
return |
1440
|
|
|
|
1441
|
|
|
|
1442
|
|
|
|
1443
|
|
|
|