Total Complexity | 215 |
Total Lines | 1400 |
Duplicated Lines | 3.5 % |
Changes | 28 | ||
Bugs | 5 | Features | 1 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like PulsedMeasurementLogic often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | # -*- coding: utf-8 -*- |
||
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 'laser_ignore_list' in settings_dict: |
||
694 | self._laser_ignore_list = sorted(settings_dict.get('laser_ignore_list')) |
||
695 | if 'alternating' in settings_dict: |
||
696 | self._alternating = bool(settings_dict.get('alternating')) |
||
697 | |||
698 | # Perform sanity checks on settings |
||
699 | self._measurement_settings_sanity_check() |
||
700 | |||
701 | # emit update signal for master (GUI or other logic module) |
||
702 | self.sigMeasurementSettingsUpdated.emit(self.measurement_settings) |
||
703 | return self.measurement_settings |
||
704 | |||
705 | @QtCore.Slot(bool, str) |
||
706 | def toggle_pulsed_measurement(self, start, stash_raw_data_tag=''): |
||
707 | """ |
||
708 | Convenience method to start/stop measurement |
||
709 | |||
710 | @param bool start: Start the measurement (True) or stop the measurement (False) |
||
711 | """ |
||
712 | if start: |
||
713 | self.start_pulsed_measurement(stash_raw_data_tag) |
||
714 | else: |
||
715 | self.stop_pulsed_measurement(stash_raw_data_tag) |
||
716 | return |
||
717 | |||
718 | @QtCore.Slot(str) |
||
719 | def start_pulsed_measurement(self, stashed_raw_data_tag=''): |
||
720 | """Start the analysis loop.""" |
||
721 | self.sigMeasurementStatusUpdated.emit(True, False) |
||
722 | |||
723 | # Check if measurement settings need to be invoked |
||
724 | if self._invoke_settings_from_sequence: |
||
725 | if self._measurement_information: |
||
726 | self._apply_invoked_settings() |
||
727 | self.sigMeasurementSettingsUpdated.emit(self.measurement_settings) |
||
728 | else: |
||
729 | # abort measurement if settings could not be invoked |
||
730 | self.log.error('Unable to invoke measurement settings.\nThis feature can only be ' |
||
731 | 'used when creating the pulse sequence via predefined methods.\n' |
||
732 | 'Aborting measurement start.') |
||
733 | self.set_measurement_settings(invoke_settings=False) |
||
734 | self.sigMeasurementStatusUpdated.emit(False, False) |
||
735 | return |
||
736 | |||
737 | with self._threadlock: |
||
738 | if self.module_state() == 'idle': |
||
739 | # Lock module state |
||
740 | self.module_state.lock() |
||
741 | |||
742 | # Clear previous fits |
||
743 | self.fc.clear_result() |
||
744 | |||
745 | # initialize data arrays |
||
746 | self._initialize_data_arrays() |
||
747 | |||
748 | # recall stashed raw data |
||
749 | if stashed_raw_data_tag in self._saved_raw_data: |
||
750 | self._recalled_raw_data_tag = stashed_raw_data_tag |
||
751 | self.log.info('Starting pulsed measurement with stashed raw data "{0}".' |
||
752 | ''.format(stashed_raw_data_tag)) |
||
753 | else: |
||
754 | self._recalled_raw_data_tag = None |
||
755 | |||
756 | # start microwave source |
||
757 | if self.__use_ext_microwave: |
||
758 | self.microwave_on() |
||
759 | # start fast counter |
||
760 | self.fast_counter_on() |
||
761 | # start pulse generator |
||
762 | self.pulse_generator_on() |
||
763 | |||
764 | # initialize analysis_timer |
||
765 | self.__elapsed_time = 0.0 |
||
766 | self.sigTimerUpdated.emit(self.__elapsed_time, |
||
767 | self.__elapsed_sweeps, |
||
768 | self.__timer_interval) |
||
769 | |||
770 | # Set starting time and start timer (if present) |
||
771 | self.__start_time = time.time() |
||
772 | self.sigStartTimer.emit() |
||
773 | |||
774 | # Set measurement paused flag |
||
775 | self.__is_paused = False |
||
776 | else: |
||
777 | self.log.warning('Unable to start pulsed measurement. Measurement already running.') |
||
778 | return |
||
779 | |||
780 | @QtCore.Slot(str) |
||
781 | def stop_pulsed_measurement(self, stash_raw_data_tag=''): |
||
782 | """ |
||
783 | Stop the measurement |
||
784 | """ |
||
785 | # Get raw data and analyze it a last time just before stopping the measurement. |
||
786 | try: |
||
787 | self._pulsed_analysis_loop() |
||
788 | except: |
||
789 | pass |
||
790 | |||
791 | with self._threadlock: |
||
792 | if self.module_state() == 'locked': |
||
793 | # stopping the timer |
||
794 | self.sigStopTimer.emit() |
||
795 | # Turn off fast counter |
||
796 | self.fast_counter_off() |
||
797 | # Turn off pulse generator |
||
798 | self.pulse_generator_off() |
||
799 | # Turn off microwave source |
||
800 | if self.__use_ext_microwave: |
||
801 | self.microwave_off() |
||
802 | |||
803 | # stash raw data if requested |
||
804 | if stash_raw_data_tag: |
||
805 | self.log.info('Raw data saved with tag "{0}" to continue measurement at a ' |
||
806 | 'later point.') |
||
807 | self._saved_raw_data[stash_raw_data_tag] = self.raw_data.copy() |
||
808 | self._recalled_raw_data_tag = None |
||
809 | |||
810 | # Set measurement paused flag |
||
811 | self.__is_paused = False |
||
812 | |||
813 | self.module_state.unlock() |
||
814 | self.sigMeasurementStatusUpdated.emit(False, False) |
||
815 | return |
||
816 | |||
817 | @QtCore.Slot(bool) |
||
818 | def toggle_measurement_pause(self, pause): |
||
819 | """ |
||
820 | Convenience method to pause/continue measurement |
||
821 | |||
822 | @param bool pause: Pause the measurement (True) or continue the measurement (False) |
||
823 | """ |
||
824 | if pause: |
||
825 | self.pause_pulsed_measurement() |
||
826 | else: |
||
827 | self.continue_pulsed_measurement() |
||
828 | return |
||
829 | |||
830 | View Code Duplication | @QtCore.Slot() |
|
|
|||
831 | def pause_pulsed_measurement(self): |
||
832 | """ |
||
833 | Pauses the measurement |
||
834 | """ |
||
835 | with self._threadlock: |
||
836 | if self.module_state() == 'locked': |
||
837 | # pausing the timer |
||
838 | if self.__analysis_timer.isActive(): |
||
839 | # stopping the timer |
||
840 | self.sigStopTimer.emit() |
||
841 | |||
842 | self.fast_counter_pause() |
||
843 | self.pulse_generator_off() |
||
844 | if self.__use_ext_microwave: |
||
845 | self.microwave_off() |
||
846 | |||
847 | # Set measurement paused flag |
||
848 | self.__is_paused = True |
||
849 | |||
850 | self.sigMeasurementStatusUpdated.emit(True, True) |
||
851 | else: |
||
852 | self.log.warning('Unable to pause pulsed measurement. No measurement running.') |
||
853 | self.sigMeasurementStatusUpdated.emit(False, False) |
||
854 | return |
||
855 | |||
856 | View Code Duplication | @QtCore.Slot() |
|
857 | def continue_pulsed_measurement(self): |
||
858 | """ |
||
859 | Continues the measurement |
||
860 | """ |
||
861 | with self._threadlock: |
||
862 | if self.module_state() == 'locked': |
||
863 | if self.__use_ext_microwave: |
||
864 | self.microwave_on() |
||
865 | self.fast_counter_continue() |
||
866 | self.pulse_generator_on() |
||
867 | |||
868 | # un-pausing the timer |
||
869 | if not self.__analysis_timer.isActive(): |
||
870 | self.sigStartTimer.emit() |
||
871 | |||
872 | # Set measurement paused flag |
||
873 | self.__is_paused = False |
||
874 | |||
875 | self.sigMeasurementStatusUpdated.emit(True, False) |
||
876 | else: |
||
877 | self.log.warning('Unable to continue pulsed measurement. No measurement running.') |
||
878 | self.sigMeasurementStatusUpdated.emit(False, False) |
||
879 | return |
||
880 | |||
881 | @QtCore.Slot(float) |
||
882 | @QtCore.Slot(int) |
||
883 | def set_timer_interval(self, interval): |
||
884 | """ |
||
885 | Change the interval of the measurement analysis timer |
||
886 | |||
887 | @param int|float interval: Interval of the timer in s |
||
888 | """ |
||
889 | with self._threadlock: |
||
890 | self.__timer_interval = interval |
||
891 | if self.__timer_interval > 0: |
||
892 | self.__analysis_timer.setInterval(int(1000. * self.__timer_interval)) |
||
893 | if self.module_state() == 'locked' and not self.__is_paused: |
||
894 | self.sigStartTimer.emit() |
||
895 | else: |
||
896 | self.sigStopTimer.emit() |
||
897 | |||
898 | self.sigTimerUpdated.emit(self.__elapsed_time, self.__elapsed_sweeps, |
||
899 | self.__timer_interval) |
||
900 | return |
||
901 | |||
902 | @QtCore.Slot(str) |
||
903 | def set_alternative_data_type(self, alt_data_type): |
||
904 | """ |
||
905 | |||
906 | @param alt_data_type: |
||
907 | @return: |
||
908 | """ |
||
909 | with self._threadlock: |
||
910 | if alt_data_type == 'Delta' and not self._alternating: |
||
911 | if self._alternative_data_type == 'Delta': |
||
912 | self._alternative_data_type = None |
||
913 | self.log.error('Can not set "Delta" as alternative data calculation if measurement is ' |
||
914 | 'not alternating.\n' |
||
915 | 'Setting to previous type "{0}".'.format(self.alternative_data_type)) |
||
916 | else: |
||
917 | self._alternative_data_type = alt_data_type |
||
918 | |||
919 | self._compute_alt_data() |
||
920 | self.sigMeasurementDataUpdated.emit() |
||
921 | return |
||
922 | |||
923 | @QtCore.Slot() |
||
924 | def manually_pull_data(self): |
||
925 | """ Analyse and display the data |
||
926 | """ |
||
927 | if self.module_state() == 'locked': |
||
928 | self._pulsed_analysis_loop() |
||
929 | return |
||
930 | |||
931 | @QtCore.Slot(str) |
||
932 | @QtCore.Slot(str, np.ndarray) |
||
933 | def do_fit(self, fit_method, data=None): |
||
934 | """ |
||
935 | Performs the chosen fit on the measured data. |
||
936 | |||
937 | @param str fit_method: name of the fit method to use |
||
938 | @param 2D numpy.ndarray data: the x and y data points for the fit (shape=(2,X)) |
||
939 | |||
940 | @return (2D numpy.ndarray, result object): the resulting fit data and the fit result object |
||
941 | """ |
||
942 | # Set current fit |
||
943 | self.fc.set_current_fit(fit_method) |
||
944 | |||
945 | if data is None: |
||
946 | data = self.signal_data |
||
947 | update_fit_data = True |
||
948 | else: |
||
949 | update_fit_data = False |
||
950 | |||
951 | x_fit, y_fit, result = self.fc.do_fit(data[0], data[1]) |
||
952 | |||
953 | fit_data = np.array([x_fit, y_fit]) |
||
954 | fit_name = self.fc.current_fit |
||
955 | fit_result = self.fc.current_fit_result |
||
956 | |||
957 | if update_fit_data: |
||
958 | self.signal_fit_data = fit_data |
||
959 | self.sigFitUpdated.emit(fit_name, self.signal_fit_data, fit_result) |
||
960 | |||
961 | return fit_data, fit_result |
||
962 | |||
963 | def _apply_invoked_settings(self): |
||
964 | """ |
||
965 | """ |
||
966 | if not isinstance(self._measurement_information, dict) or not self._measurement_information: |
||
967 | self.log.warning('Can\'t invoke measurement settings from sequence information ' |
||
968 | 'since no measurement_information container is given.') |
||
969 | return |
||
970 | |||
971 | # First try to set parameters that can be changed during a running measurement |
||
972 | if 'units' in self._measurement_information: |
||
973 | with self._threadlock: |
||
974 | self._data_units = self._measurement_information.get('units') |
||
975 | |||
976 | # Check if a measurement is running and apply following settings if this is not the case |
||
977 | if self.module_state() == 'locked': |
||
978 | return |
||
979 | |||
980 | if 'number_of_lasers' in self._measurement_information: |
||
981 | self._number_of_lasers = int(self._measurement_information.get('number_of_lasers')) |
||
982 | else: |
||
983 | self.log.error('Unable to invoke setting for "number_of_lasers".\n' |
||
984 | 'Measurement information container is incomplete/invalid.') |
||
985 | return |
||
986 | |||
987 | if 'laser_ignore_list' in self._measurement_information: |
||
988 | self._laser_ignore_list = sorted(self._measurement_information.get('laser_ignore_list')) |
||
989 | else: |
||
990 | self.log.error('Unable to invoke setting for "laser_ignore_list".\n' |
||
991 | 'Measurement information container is incomplete/invalid.') |
||
992 | return |
||
993 | |||
994 | if 'alternating' in self._measurement_information: |
||
995 | self._alternating = bool(self._measurement_information.get('alternating')) |
||
996 | else: |
||
997 | self.log.error('Unable to invoke setting for "alternating".\n' |
||
998 | 'Measurement information container is incomplete/invalid.') |
||
999 | return |
||
1000 | |||
1001 | if 'controlled_variable' in self._measurement_information: |
||
1002 | self._controlled_variable = np.array( |
||
1003 | self._measurement_information.get('controlled_variable'), dtype=float) |
||
1004 | else: |
||
1005 | self.log.error('Unable to invoke setting for "controlled_variable".\n' |
||
1006 | 'Measurement information container is incomplete/invalid.') |
||
1007 | return |
||
1008 | |||
1009 | if 'counting_length' in self._measurement_information: |
||
1010 | fast_counter_record_length = self._measurement_information.get('counting_length') |
||
1011 | else: |
||
1012 | self.log.error('Unable to invoke setting for "counting_length".\n' |
||
1013 | 'Measurement information container is incomplete/invalid.') |
||
1014 | return |
||
1015 | |||
1016 | if self.fastcounter().is_gated(): |
||
1017 | self.set_fast_counter_settings(number_of_gates=self._number_of_lasers, |
||
1018 | record_length=fast_counter_record_length) |
||
1019 | else: |
||
1020 | self.set_fast_counter_settings(record_length=fast_counter_record_length) |
||
1021 | return |
||
1022 | |||
1023 | def _measurement_settings_sanity_check(self): |
||
1024 | number_of_analyzed_lasers = self._number_of_lasers - len(self._laser_ignore_list) |
||
1025 | if len(self._controlled_variable) < 1: |
||
1026 | self.log.error('Tried to set empty controlled variables array. This can not work.') |
||
1027 | |||
1028 | if self._alternating and (number_of_analyzed_lasers // 2) != len(self._controlled_variable): |
||
1029 | self.log.error('Half of the number of laser pulses to analyze ({0}) does not match the ' |
||
1030 | 'number of controlled_variable ticks ({1:d}).' |
||
1031 | ''.format(number_of_analyzed_lasers // 2, |
||
1032 | len(self._controlled_variable))) |
||
1033 | elif not self._alternating and number_of_analyzed_lasers != len(self._controlled_variable): |
||
1034 | self.log.error('Number of laser pulses to analyze ({0:d}) does not match the number of ' |
||
1035 | 'controlled_variable ticks ({1:d}).' |
||
1036 | ''.format(number_of_analyzed_lasers, len(self._controlled_variable))) |
||
1037 | |||
1038 | if self.fastcounter().is_gated() and self._number_of_lasers != self.__fast_counter_gates: |
||
1039 | self.log.error('Gated fast counter gate number differs from number of laser pulses ' |
||
1040 | 'configured in measurement settings.') |
||
1041 | return |
||
1042 | |||
1043 | def _pulsed_analysis_loop(self): |
||
1044 | """ Acquires laser pulses from fast counter, |
||
1045 | calculates fluorescence signal and creates plots. |
||
1046 | """ |
||
1047 | with self._threadlock: |
||
1048 | if self.module_state() == 'locked': |
||
1049 | # Update elapsed time |
||
1050 | self.__elapsed_time = time.time() - self.__start_time |
||
1051 | |||
1052 | # Get counter raw data (including recalled raw data from previous measurement) |
||
1053 | self.raw_data = self._get_raw_data() |
||
1054 | |||
1055 | # extract laser pulses from raw data |
||
1056 | return_dict = self._pulseextractor.extract_laser_pulses(self.raw_data) |
||
1057 | self.laser_data = return_dict['laser_counts_arr'] |
||
1058 | |||
1059 | # analyze pulses and get data points for signal array. Also check if extraction |
||
1060 | # worked (non-zero array returned). |
||
1061 | if self.laser_data.any(): |
||
1062 | tmp_signal, tmp_error = self._pulseanalyzer.analyse_laser_pulses( |
||
1063 | self.laser_data) |
||
1064 | else: |
||
1065 | tmp_signal = np.zeros(self.laser_data.shape[0]) |
||
1066 | tmp_error = np.zeros(self.laser_data.shape[0]) |
||
1067 | |||
1068 | # exclude laser pulses to ignore |
||
1069 | if len(self._laser_ignore_list) > 0: |
||
1070 | # Convert relative negative indices into absolute positive indices |
||
1071 | while self._laser_ignore_list[0] < 0: |
||
1072 | neg_index = self._laser_ignore_list[0] |
||
1073 | self._laser_ignore_list[0] = len(tmp_signal) + neg_index |
||
1074 | self._laser_ignore_list.sort() |
||
1075 | |||
1076 | tmp_signal = np.delete(tmp_signal, self._laser_ignore_list) |
||
1077 | tmp_error = np.delete(tmp_error, self._laser_ignore_list) |
||
1078 | |||
1079 | # order data according to alternating flag |
||
1080 | if self._alternating: |
||
1081 | self.signal_data[1] = tmp_signal[::2] |
||
1082 | self.signal_data[2] = tmp_signal[1::2] |
||
1083 | self.measurement_error[1] = tmp_error[::2] |
||
1084 | self.measurement_error[2] = tmp_error[1::2] |
||
1085 | else: |
||
1086 | self.signal_data[1] = tmp_signal |
||
1087 | self.measurement_error[1] = tmp_error |
||
1088 | |||
1089 | # Compute alternative data array from signal |
||
1090 | self._compute_alt_data() |
||
1091 | |||
1092 | # emit signals |
||
1093 | self.sigTimerUpdated.emit(self.__elapsed_time, self.__elapsed_sweeps, |
||
1094 | self.__timer_interval) |
||
1095 | self.sigMeasurementDataUpdated.emit() |
||
1096 | return |
||
1097 | |||
1098 | def _get_raw_data(self): |
||
1099 | """ |
||
1100 | Get the raw count data from the fast counting hardware and perform sanity checks. |
||
1101 | Also add recalled raw data to the newly received data. |
||
1102 | :return numpy.ndarray: The count data (1D for ungated, 2D for gated counter) |
||
1103 | """ |
||
1104 | # get raw data from fast counter |
||
1105 | fc_data = netobtain(self.fastcounter().get_data_trace()) |
||
1106 | |||
1107 | # add old raw data from previous measurements if necessary |
||
1108 | if self._saved_raw_data.get(self._recalled_raw_data_tag) is not None: |
||
1109 | self.log.info('Found old saved raw data with tag "{0}".' |
||
1110 | ''.format(self._recalled_raw_data_tag)) |
||
1111 | if not fc_data.any(): |
||
1112 | self.log.warning('Only zeros received from fast counter!\n' |
||
1113 | 'Using recalled raw data only.') |
||
1114 | fc_data = self._saved_raw_data[self._recalled_raw_data_tag] |
||
1115 | elif self._saved_raw_data[self._recalled_raw_data_tag].shape == fc_data.shape: |
||
1116 | self.log.debug('Recalled raw data has the same shape as current data.') |
||
1117 | fc_data = self._saved_raw_data[self._recalled_raw_data_tag] + fc_data |
||
1118 | else: |
||
1119 | self.log.warning('Recalled raw data has not the same shape as current data.' |
||
1120 | '\nDid NOT add recalled raw data to current time trace.') |
||
1121 | elif not fc_data.any(): |
||
1122 | self.log.warning('Only zeros received from fast counter!') |
||
1123 | fc_data = np.zeros(fc_data.shape, dtype='int64') |
||
1124 | return fc_data |
||
1125 | |||
1126 | def _initialize_data_arrays(self): |
||
1127 | """ |
||
1128 | Initializing the signal, error, laser and raw data arrays. |
||
1129 | """ |
||
1130 | # Determine signal array dimensions |
||
1131 | signal_dim = 3 if self._alternating else 2 |
||
1132 | |||
1133 | self.signal_data = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
||
1134 | self.signal_data[0] = self._controlled_variable |
||
1135 | |||
1136 | self.signal_alt_data = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
||
1137 | self.signal_alt_data[0] = self._controlled_variable |
||
1138 | |||
1139 | self.measurement_error = np.zeros((signal_dim, len(self._controlled_variable)), dtype=float) |
||
1140 | self.measurement_error[0] = self._controlled_variable |
||
1141 | |||
1142 | number_of_bins = int(self.__fast_counter_record_length / self.__fast_counter_binwidth) |
||
1143 | laser_length = number_of_bins if self.__fast_counter_gates > 0 else 500 |
||
1144 | self.laser_data = np.zeros((self._number_of_lasers, laser_length), dtype='int64') |
||
1145 | |||
1146 | if self.__fast_counter_gates > 0: |
||
1147 | self.raw_data = np.zeros((self._number_of_lasers, number_of_bins), dtype='int64') |
||
1148 | else: |
||
1149 | self.raw_data = np.zeros(number_of_bins, dtype='int64') |
||
1150 | |||
1151 | self.sigMeasurementDataUpdated.emit() |
||
1152 | return |
||
1153 | |||
1154 | # FIXME: Revise everything below |
||
1155 | |||
1156 | ############################################################################ |
||
1157 | @QtCore.Slot(str, bool) |
||
1158 | def save_measurement_data(self, tag=None, with_error=True): |
||
1159 | """ |
||
1160 | Prepare data to be saved and create a proper plot of the data |
||
1161 | |||
1162 | @param str tag: a filetag which will be included in the filename |
||
1163 | @param bool with_error: select whether errors should be saved/plotted |
||
1164 | |||
1165 | @return str: filepath where data were saved |
||
1166 | """ |
||
1167 | filepath = self.savelogic().get_path_for_module('PulsedMeasurement') |
||
1168 | timestamp = datetime.datetime.now() |
||
1169 | |||
1170 | ##################################################################### |
||
1171 | #### Save extracted laser pulses #### |
||
1172 | ##################################################################### |
||
1173 | if tag is not None and len(tag) > 0: |
||
1174 | filelabel = tag + '_laser_pulses' |
||
1175 | else: |
||
1176 | filelabel = 'laser_pulses' |
||
1177 | |||
1178 | # prepare the data in a dict or in an OrderedDict: |
||
1179 | data = OrderedDict() |
||
1180 | laser_trace = self.laser_data |
||
1181 | data['Signal (counts)'.format()] = laser_trace.transpose() |
||
1182 | |||
1183 | # write the parameters: |
||
1184 | parameters = OrderedDict() |
||
1185 | parameters['bin width (s)'] = self.__fast_counter_binwidth |
||
1186 | parameters['record length (s)'] = self.__fast_counter_record_length |
||
1187 | parameters['gated counting'] = self.fast_counter_settings['is_gated'] |
||
1188 | parameters['extraction parameters'] = self.extraction_settings |
||
1189 | |||
1190 | self.savelogic().save_data(data, |
||
1191 | timestamp=timestamp, |
||
1192 | parameters=parameters, |
||
1193 | filepath=filepath, |
||
1194 | filelabel=filelabel, |
||
1195 | filetype='text', |
||
1196 | fmt='%d', |
||
1197 | delimiter='\t') |
||
1198 | |||
1199 | ##################################################################### |
||
1200 | #### Save measurement data #### |
||
1201 | ##################################################################### |
||
1202 | if tag is not None and len(tag) > 0: |
||
1203 | filelabel = tag + '_pulsed_measurement' |
||
1204 | else: |
||
1205 | filelabel = 'pulsed_measurement' |
||
1206 | |||
1207 | # prepare the data in a dict or in an OrderedDict: |
||
1208 | header_str = 'Controlled variable({0})\tSignal({1})\t'.format(*self._data_units) |
||
1209 | if self._alternating: |
||
1210 | header_str += 'Signal2({0})'.format(self._data_units[1]) |
||
1211 | if with_error: |
||
1212 | header_str += 'Error({0})'.format(self._data_units[1]) |
||
1213 | if self._alternating: |
||
1214 | header_str += 'Error2({0})'.format(self._data_units[1]) |
||
1215 | data = OrderedDict() |
||
1216 | if with_error: |
||
1217 | data[header_str] = np.vstack((self.signal_data, self.measurement_error)).transpose() |
||
1218 | else: |
||
1219 | data[header_str] = self.signal_data.transpose() |
||
1220 | |||
1221 | # write the parameters: |
||
1222 | parameters = OrderedDict() |
||
1223 | parameters['Approx. measurement time (s)'] = self.__elapsed_time |
||
1224 | parameters['Measurement sweeps'] = self.__elapsed_sweeps |
||
1225 | parameters['Number of laser pulses'] = self._number_of_lasers |
||
1226 | parameters['Laser ignore indices'] = self._laser_ignore_list |
||
1227 | parameters['alternating'] = self._alternating |
||
1228 | parameters['analysis parameters'] = self.analysis_settings |
||
1229 | parameters['extraction parameters'] = self.extraction_settings |
||
1230 | parameters['fast counter settings'] = self.fast_counter_settings |
||
1231 | |||
1232 | # Prepare the figure to save as a "data thumbnail" |
||
1233 | plt.style.use(self.savelogic().mpl_qd_style) |
||
1234 | |||
1235 | # extract the possible colors from the colorscheme: |
||
1236 | prop_cycle = self.savelogic().mpl_qd_style['axes.prop_cycle'] |
||
1237 | colors = {} |
||
1238 | for i, color_setting in enumerate(prop_cycle): |
||
1239 | colors[i] = color_setting['color'] |
||
1240 | |||
1241 | # scale the x_axis for plotting |
||
1242 | max_val = np.max(self.signal_data[0]) |
||
1243 | scaled_float = units.ScaledFloat(max_val) |
||
1244 | counts_prefix = scaled_float.scale |
||
1245 | x_axis_scaled = self.signal_data[0] / scaled_float.scale_val |
||
1246 | |||
1247 | # Create the figure object |
||
1248 | if self._alternative_data_type: |
||
1249 | fig, (ax1, ax2) = plt.subplots(2, 1) |
||
1250 | else: |
||
1251 | fig, ax1 = plt.subplots() |
||
1252 | |||
1253 | if with_error: |
||
1254 | ax1.errorbar(x=x_axis_scaled, y=self.signal_data[1], |
||
1255 | yerr=self.measurement_error[1], fmt='-o', |
||
1256 | linestyle=':', linewidth=0.5, color=colors[0], |
||
1257 | ecolor=colors[1], capsize=3, capthick=0.9, |
||
1258 | elinewidth=1.2, label='data trace 1') |
||
1259 | |||
1260 | if self._alternating: |
||
1261 | ax1.errorbar(x=x_axis_scaled, y=self.signal_data[2], |
||
1262 | yerr=self.measurement_error[2], fmt='-D', |
||
1263 | linestyle=':', linewidth=0.5, color=colors[3], |
||
1264 | ecolor=colors[4], capsize=3, capthick=0.7, |
||
1265 | elinewidth=1.2, label='data trace 2') |
||
1266 | else: |
||
1267 | ax1.plot(x_axis_scaled, self.signal_data[1], '-o', color=colors[0], |
||
1268 | linestyle=':', linewidth=0.5, label='data trace 1') |
||
1269 | |||
1270 | if self._alternating: |
||
1271 | ax1.plot(x_axis_scaled, self.signal_data[2], '-o', |
||
1272 | color=colors[3], linestyle=':', linewidth=0.5, |
||
1273 | label='data trace 2') |
||
1274 | |||
1275 | # Do not include fit curve if there is no fit calculated. |
||
1276 | if self.signal_fit_data.size != 0 and np.max(self.signal_fit_data[1]) > 0: |
||
1277 | x_axis_fit_scaled = self.signal_fit_data[0] / scaled_float.scale_val |
||
1278 | ax1.plot(x_axis_fit_scaled, self.signal_fit_data[1], |
||
1279 | color=colors[2], marker='None', linewidth=1.5, |
||
1280 | label='fit: {0}'.format(self.fc.current_fit)) |
||
1281 | |||
1282 | # add then the fit result to the plot: |
||
1283 | |||
1284 | # Parameters for the text plot: |
||
1285 | # The position of the text annotation is controlled with the |
||
1286 | # relative offset in x direction and the relative length factor |
||
1287 | # rel_len_fac of the longest entry in one column |
||
1288 | rel_offset = 0.02 |
||
1289 | rel_len_fac = 0.011 |
||
1290 | entries_per_col = 24 |
||
1291 | |||
1292 | # create the formatted fit text: |
||
1293 | if hasattr(self.fc.current_fit_result, 'result_str_dict'): |
||
1294 | fit_res = units.create_formatted_output(self.fc.current_fit_result.result_str_dict) |
||
1295 | else: |
||
1296 | self.log.warning('The fit container does not contain any data ' |
||
1297 | 'from the fit! Apply the fit once again.') |
||
1298 | fit_res = '' |
||
1299 | # do reverse processing to get each entry in a list |
||
1300 | entry_list = fit_res.split('\n') |
||
1301 | # slice the entry_list in entries_per_col |
||
1302 | chunks = [entry_list[x:x+entries_per_col] for x in range(0, len(entry_list), entries_per_col)] |
||
1303 | |||
1304 | is_first_column = True # first entry should contain header or \n |
||
1305 | shift = rel_offset |
||
1306 | |||
1307 | for column in chunks: |
||
1308 | |||
1309 | max_length = max(column, key=len) # get the longest entry |
||
1310 | column_text = '' |
||
1311 | |||
1312 | for entry in column: |
||
1313 | column_text += entry + '\n' |
||
1314 | |||
1315 | column_text = column_text[:-1] # remove the last new line |
||
1316 | |||
1317 | heading = '' |
||
1318 | if is_first_column: |
||
1319 | heading = 'Fit results:' |
||
1320 | |||
1321 | column_text = heading + '\n' + column_text |
||
1322 | |||
1323 | ax1.text(1.00 + shift, 0.99, column_text, |
||
1324 | verticalalignment='top', |
||
1325 | horizontalalignment='left', |
||
1326 | transform=ax1.transAxes, |
||
1327 | fontsize=12) |
||
1328 | |||
1329 | # the shift in position of the text is a linear function |
||
1330 | # which depends on the longest entry in the column |
||
1331 | shift += rel_len_fac * len(max_length) |
||
1332 | |||
1333 | is_first_column = False |
||
1334 | |||
1335 | # handle the save of the alternative data plot |
||
1336 | if self._alternative_data_type: |
||
1337 | |||
1338 | # scale the x_axis for plotting |
||
1339 | max_val = np.max(self.signal_alt_data[0]) |
||
1340 | scaled_float = units.ScaledFloat(max_val) |
||
1341 | x_axis_prefix = scaled_float.scale |
||
1342 | x_axis_ft_scaled = self.signal_alt_data[0] / scaled_float.scale_val |
||
1343 | |||
1344 | # since no ft units are provided, make a small work around: |
||
1345 | if self._alternative_data_type == 'FFT': |
||
1346 | if self._data_units[0] == 's': |
||
1347 | inverse_cont_var = 'Hz' |
||
1348 | elif self._data_units[0] == 'Hz': |
||
1349 | inverse_cont_var = 's' |
||
1350 | else: |
||
1351 | inverse_cont_var = '(1/{0})'.format(self._data_units[0]) |
||
1352 | x_axis_ft_label = 'Fourier Transformed controlled variable (' + x_axis_prefix + inverse_cont_var + ')' |
||
1353 | y_axis_ft_label = 'Fourier amplitude (arb. u.)' |
||
1354 | ft_label = 'FT of data trace 1' |
||
1355 | else: |
||
1356 | x_axis_ft_label = 'controlled variable (' + self._data_units[0] + ')' |
||
1357 | y_axis_ft_label = 'norm. sig (arb. u.)' |
||
1358 | ft_label = '' |
||
1359 | |||
1360 | ax2.plot(x_axis_ft_scaled, self.signal_alt_data[1], '-o', |
||
1361 | linestyle=':', linewidth=0.5, color=colors[0], |
||
1362 | label=ft_label) |
||
1363 | |||
1364 | ax2.set_xlabel(x_axis_ft_label) |
||
1365 | ax2.set_ylabel(y_axis_ft_label) |
||
1366 | ax2.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
||
1367 | mode="expand", borderaxespad=0.) |
||
1368 | |||
1369 | #FIXME: no fit plot for the alternating graph, use for that graph colors[5] |
||
1370 | ax1.set_xlabel('controlled variable (' + counts_prefix + self._data_units[0] + ')') |
||
1371 | ax1.set_ylabel('signal (' + self._data_units[1] + ')') |
||
1372 | |||
1373 | fig.tight_layout() |
||
1374 | ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
||
1375 | mode="expand", borderaxespad=0.) |
||
1376 | # plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, |
||
1377 | # mode="expand", borderaxespad=0.) |
||
1378 | |||
1379 | self.savelogic().save_data(data, timestamp=timestamp, |
||
1380 | parameters=parameters, fmt='%.15e', |
||
1381 | filepath=filepath, filelabel=filelabel, filetype='text', |
||
1382 | delimiter='\t', plotfig=fig) |
||
1383 | |||
1384 | ##################################################################### |
||
1385 | #### Save raw data timetrace #### |
||
1386 | ##################################################################### |
||
1387 | filelabel = 'raw_timetrace' if not tag else tag + '_raw_timetrace' |
||
1388 | |||
1389 | # prepare the data in a dict or in an OrderedDict: |
||
1390 | data = OrderedDict() |
||
1391 | raw_trace = self.raw_data.astype('int64') |
||
1392 | data['Signal(counts)'] = raw_trace.transpose() |
||
1393 | # write the parameters: |
||
1394 | parameters = OrderedDict() |
||
1395 | parameters['bin width (s)'] = self.__fast_counter_binwidth |
||
1396 | parameters['record length (s)'] = self.__fast_counter_record_length |
||
1397 | parameters['gated counting'] = self.fast_counter_settings['is_gated'] |
||
1398 | parameters['Number of laser pulses'] = self._number_of_lasers |
||
1399 | parameters['alternating'] = self._alternating |
||
1400 | parameters['Controlled variable'] = list(self.signal_data[0]) |
||
1401 | |||
1402 | self.savelogic().save_data(data, timestamp=timestamp, |
||
1403 | parameters=parameters, fmt='%d', |
||
1404 | filepath=filepath, filelabel=filelabel, |
||
1405 | filetype=self._raw_data_save_type, |
||
1406 | delimiter='\t') |
||
1407 | return filepath |
||
1408 | |||
1409 | def _compute_alt_data(self): |
||
1410 | """ |
||
1411 | Performing transformations on the measurement data (e.g. fourier transform). |
||
1412 | """ |
||
1413 | if self._alternative_data_type == 'Delta' and len(self.signal_data) == 3: |
||
1414 | self.signal_alt_data = np.empty((2, self.signal_data.shape[1]), dtype=float) |
||
1415 | self.signal_alt_data[0] = self.signal_data[0] |
||
1416 | self.signal_alt_data[1] = self.signal_data[1] - self.signal_data[2] |
||
1417 | elif self._alternative_data_type == 'FFT' and self.signal_data.shape[1] >= 2: |
||
1418 | fft_x, fft_y = units.compute_ft(x_val=self.signal_data[0], |
||
1419 | y_val=self.signal_data[1], |
||
1420 | zeropad_num=self.zeropad, |
||
1421 | window=self.window, |
||
1422 | base_corr=self.base_corr, |
||
1423 | psd=self.psd) |
||
1424 | self.signal_alt_data = np.empty((len(self.signal_data), len(fft_x)), dtype=float) |
||
1425 | self.signal_alt_data[0] = fft_x |
||
1426 | self.signal_alt_data[1] = fft_y |
||
1427 | for dim in range(2, len(self.signal_data)): |
||
1428 | dummy, self.signal_alt_data[dim] = units.compute_ft(x_val=self.signal_data[0], |
||
1429 | y_val=self.signal_data[dim], |
||
1430 | zeropad_num=self.zeropad, |
||
1431 | window=self.window, |
||
1432 | base_corr=self.base_corr, |
||
1433 | psd=self.psd) |
||
1434 | else: |
||
1435 | self.signal_alt_data = np.zeros(self.signal_data.shape, dtype=float) |
||
1436 | self.signal_alt_data[0] = self.signal_data[0] |
||
1437 | return |
||
1438 | |||
1441 |