Completed
Pull Request — master (#387)
by Jan
05:42
created

ConfocalLogic.initialize_image()   F

Complexity

Conditions 9

Size

Total Lines 130

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
c 1
b 0
f 0
dl 0
loc 130
rs 3.1304

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
"""
3
This module operates a confocal microsope.
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
from copy import copy
25
import time
26
import datetime
27
import numpy as np
28
import matplotlib as mpl
29
import matplotlib.pyplot as plt
30
from io import BytesIO
31
32
from logic.generic_logic import GenericLogic
33
from core.util.mutex import Mutex
34
from core.module import Connector, ConfigOption, StatusVar
35
36
37
class OldConfigFileError(Exception):
38
    """ Exception that is thrown when an old config file is loaded.
39
    """
40
    def __init__(self):
41
        super().__init__('Old configuration file detected. Ignoring confocal history.')
42
43
44
class ConfocalHistoryEntry(QtCore.QObject):
45
    """ This class contains all relevant parameters of a Confocal scan.
46
        It provides methods to extract, restore and serialize this data.
47
    """
48
49
    def __init__(self, confocal):
50
        """ Make a confocal data setting with default values. """
51
        super().__init__()
52
53
        self.depth_scan_dir_is_xz = True
54
        self.depth_img_is_xz = True
55
56
        self.xy_line_pos = 0
57
        self.depth_line_pos = 0
58
59
        # Reads in the maximal scanning range. The unit of that scan range is meters!
60
        self.x_range = confocal._scanning_device.get_position_range()[0]
61
        self.y_range = confocal._scanning_device.get_position_range()[1]
62
        self.z_range = confocal._scanning_device.get_position_range()[2]
63
64
        # Sets the current position to the center of the maximal scanning range
65
        self.current_x = (self.x_range[0] + self.x_range[1]) / 2
66
        self.current_y = (self.y_range[0] + self.y_range[1]) / 2
67
        self.current_z = (self.z_range[0] + self.z_range[1]) / 2
68
        self.current_a = 0.0
69
70
        # Sets the size of the image to the maximal scanning range
71
        self.image_x_range = self.x_range
72
        self.image_y_range = self.y_range
73
        self.image_z_range = self.z_range
74
75
        # Default values for the resolution of the scan
76
        self.xy_resolution = 100
77
        self.z_resolution = 50
78
79
        # Initialization of internal counter for scanning
80
        self.xy_line_position = 0
81
        self.depth_line_position = 0
82
83
        # Variable to check if a scan is continuable
84
        self.scan_counter = 0
85
        self.xy_scan_continuable = False
86
        self.depth_scan_continuable = False
87
88
        # tilt correction stuff:
89
        self.tilt_correction = False
90
        # rotation point for tilt correction
91
        self.tilt_reference_x = 0.5 * (self.x_range[0] + self.x_range[1])
92
        self.tilt_reference_y = 0.5 * (self.y_range[0] + self.y_range[1])
93
        # sample slope
94
        self.tilt_slope_x = 0
95
        self.tilt_slope_y = 0
96
        # tilt correction points
97
        self.point1 = np.array((0, 0, 0))
98
        self.point2 = np.array((0, 0, 0))
99
        self.point3 = np.array((0, 0, 0))
100
        self.tilt_correction = False
101
        self.tilt_slope_x = 0
102
        self.tilt_slope_y = 0
103
        self.tilt_reference_x = 0
104
        self.tilt_reference_y = 0
105
106
    def restore(self, confocal):
107
        """ Write data back into confocal logic and pull all the necessary strings """
108
        confocal._current_x = self.current_x
109
        confocal._current_y = self.current_y
110
        confocal._current_z = self.current_z
111
        confocal._current_a = self.current_a
112
        confocal.image_x_range = np.copy(self.image_x_range)
113
        confocal.image_y_range = np.copy(self.image_y_range)
114
        confocal.image_z_range = np.copy(self.image_z_range)
115
        confocal.xy_resolution = self.xy_resolution
116
        confocal.z_resolution = self.z_resolution
117
        confocal.depth_img_is_xz = self.depth_img_is_xz
118
        confocal.depth_scan_dir_is_xz = self.depth_scan_dir_is_xz
119
        confocal._xy_line_pos = self.xy_line_position
120
        confocal._depth_line_pos = self.depth_line_position
121
        confocal._xyscan_continuable = self.xy_scan_continuable
122
        confocal._zscan_continuable = self.depth_scan_continuable
123
        confocal._scan_counter = self.scan_counter
124
        confocal.point1 = np.copy(self.point1)
125
        confocal.point2 = np.copy(self.point2)
126
        confocal.point3 = np.copy(self.point3)
127
        confocal._scanning_device.tilt_variable_ax = self.tilt_slope_x
128
        confocal._scanning_device.tilt_variable_ay = self.tilt_slope_y
129
        confocal._scanning_device.tilt_reference_x = self.tilt_reference_x
130
        confocal._scanning_device.tilt_reference_y = self.tilt_reference_y
131
        confocal._scanning_device.tiltcorrection = self.tilt_correction
132
133
        confocal.initialize_image()
134
        try:
135
            if confocal.xy_image.shape == self.xy_image.shape:
136
                confocal.xy_image = np.copy(self.xy_image)
137
        except AttributeError:
138
            self.xy_image = np.copy(confocal.xy_image)
139
140
        confocal._zscan = True
141
        confocal.initialize_image()
142
        try:
143
            if confocal.depth_image.shape == self.depth_image.shape:
144
                confocal.depth_image = np.copy(self.depth_image)
145
        except AttributeError:
146
            self.depth_image = np.copy(confocal.depth_image)
147
        confocal._zscan = False
148
149
    def snapshot(self, confocal):
150
        """ Extract all necessary data from a confocal logic and keep it for later use """
151
        self.current_x = confocal._current_x
152
        self.current_y = confocal._current_y
153
        self.current_z = confocal._current_z
154
        self.current_a = confocal._current_a
155
        self.image_x_range = np.copy(confocal.image_x_range)
156
        self.image_y_range = np.copy(confocal.image_y_range)
157
        self.image_z_range = np.copy(confocal.image_z_range)
158
        self.xy_resolution = confocal.xy_resolution
159
        self.z_resolution = confocal.z_resolution
160
        self.depth_scan_dir_is_xz = confocal.depth_scan_dir_is_xz
161
        self.depth_img_is_xz = confocal.depth_img_is_xz
162
        self.xy_line_position = confocal._xy_line_pos
163
        self.depth_line_position = confocal._depth_line_pos
164
        self.xy_scan_continuable = confocal._xyscan_continuable
165
        self.depth_scan_continuable = confocal._zscan_continuable
166
        self.scan_counter = confocal._scan_counter
167
        self.tilt_correction = confocal._scanning_device.tiltcorrection
168
        self.tilt_slope_x = confocal._scanning_device.tilt_variable_ax
169
        self.tilt_slope_y = confocal._scanning_device.tilt_variable_ay
170
        self.tilt_reference_x = confocal._scanning_device.tilt_reference_x
171
        self.tilt_reference_y = confocal._scanning_device.tilt_reference_y
172
        self.point1 = np.copy(confocal.point1)
173
        self.point2 = np.copy(confocal.point2)
174
        self.point3 = np.copy(confocal.point3)
175
        self.xy_image = np.copy(confocal.xy_image)
176
        self.depth_image = np.copy(confocal.depth_image)
177
178
    def serialize(self):
179
        """ Give out a dictionary that can be saved via the usual means """
180
        serialized = dict()
181
        serialized['focus_position'] = [self.current_x, self.current_y, self.current_z, self.current_a]
182
        serialized['x_range'] = list(self.image_x_range)
183
        serialized['y_range'] = list(self.image_y_range)
184
        serialized['z_range'] = list(self.image_z_range)
185
        serialized['xy_resolution'] = self.xy_resolution
186
        serialized['z_resolution'] = self.z_resolution
187
        serialized['depth_img_is_xz'] = self.depth_img_is_xz
188
        serialized['depth_dir_is_xz'] = self.depth_scan_dir_is_xz
189
        serialized['xy_line_position'] = self.xy_line_position
190
        serialized['depth_line_position'] = self.depth_line_position
191
        serialized['xy_scan_cont'] = self.xy_scan_continuable
192
        serialized['depth_scan_cont'] = self.depth_scan_continuable
193
        serialized['scan_counter'] = self.scan_counter
194
        serialized['tilt_correction'] = self.tilt_correction
195
        serialized['tilt_point1'] = list(self.point1)
196
        serialized['tilt_point2'] = list(self.point2)
197
        serialized['tilt_point3'] = list(self.point3)
198
        serialized['tilt_reference'] = [self.tilt_reference_x, self.tilt_reference_y]
199
        serialized['tilt_slope'] = [self.tilt_slope_x, self.tilt_slope_y]
200
        serialized['xy_image'] = self.xy_image
201
        serialized['depth_image'] = self.depth_image
202
        return serialized
203
204
    def deserialize(self, serialized):
205
        """ Restore Confocal history object from a dict """
206
        if 'focus_position' in serialized and len(serialized['focus_position']) == 4:
207
            self.current_x = serialized['focus_position'][0]
208
            self.current_y = serialized['focus_position'][1]
209
            self.current_z = serialized['focus_position'][2]
210
            self.current_a = serialized['focus_position'][3]
211
        if 'x_range' in serialized and len(serialized['x_range']) == 2:
212
            self.image_x_range = serialized['x_range']
213
        if 'y_range' in serialized and len(serialized['y_range']) == 2:
214
            self.image_y_range = serialized['y_range']
215
        if 'z_range' in serialized and len(serialized['z_range']) == 2:
216
            self.image_z_range = serialized['z_range']
217
        if 'xy_resolution' in serialized:
218
            self.xy_resolution = serialized['xy_resolution']
219
        if 'z_resolution' in serialized:
220
            self.z_resolution = serialized['z_resolution']
221
        if 'depth_img_is_xz' in serialized:
222
            self.depth_img_is_xz = serialized['depth_img_is_xz']
223
        if 'depth_dir_is_xz' in serialized:
224
            self.depth_scan_dir_is_xz = serialized['depth_dir_is_xz']
225
        if 'tilt_correction' in serialized:
226
            self.tilt_correction = serialized['tilt_correction']
227
        if 'tilt_reference' in serialized and len(serialized['tilt_reference']) == 2:
228
            self.tilt_reference_x = serialized['tilt_reference'][0]
229
            self.tilt_reference_y = serialized['tilt_reference'][1]
230
        if 'tilt_slope' in serialized and len(serialized['tilt_slope']) == 2:
231
            self.tilt_slope_x = serialized['tilt_slope'][0]
232
            self.tilt_slope_y = serialized['tilt_slope'][1]
233
        if 'tilt_point1' in serialized and len(serialized['tilt_point1']) == 3:
234
            self.point1 = np.array(serialized['tilt_point1'])
235
        if 'tilt_point2' in serialized and len(serialized['tilt_point2']) == 3:
236
            self.point2 = np.array(serialized['tilt_point2'])
237
        if 'tilt_point3' in serialized and len(serialized['tilt_point3']) == 3:
238
            self.point3 = np.array(serialized['tilt_point3'])
239
        if 'xy_image' in serialized:
240
            if isinstance(serialized['xy_image'], np.ndarray):
241
                self.xy_image = serialized['xy_image']
242
            else:
243
                raise OldConfigFileError()
244
        if 'depth_image' in serialized:
245
            if isinstance(serialized['depth_image'], np.ndarray):
246
                self.depth_image = serialized['depth_image'].copy()
247
            else:
248
                raise OldConfigFileError()
249
250
251
class ConfocalLogic(GenericLogic):
252
    """
253
    This is the Logic class for confocal scanning.
254
    """
255
    _modclass = 'confocallogic'
256
    _modtype = 'logic'
257
258
    # declare connectors
259
    confocalscanner1 = Connector(interface='ConfocalScannerInterface')
260
    savelogic = Connector(interface='SaveLogic')
261
262
    # status vars
263
    _clock_frequency = StatusVar('clock_frequency', 500)
264
    return_slowness = StatusVar(default=50)
265
    max_history_length = StatusVar(default=10)
266
267
    # signals
268
    signal_start_scanning = QtCore.Signal(str)
269
    signal_continue_scanning = QtCore.Signal(str)
270
    signal_stop_scanning = QtCore.Signal()
271
    signal_scan_lines_next = QtCore.Signal()
272
    signal_xy_image_updated = QtCore.Signal()
273
    signal_depth_image_updated = QtCore.Signal()
274
    signal_change_position = QtCore.Signal(str)
275
    signal_xy_data_saved = QtCore.Signal()
276
    signal_depth_data_saved = QtCore.Signal()
277
    signal_tilt_correction_active = QtCore.Signal(bool)
278
    signal_tilt_correction_update = QtCore.Signal()
279
    signal_draw_figure_completed = QtCore.Signal()
280
    signal_position_changed = QtCore.Signal()
281
282
    sigImageXYInitialized = QtCore.Signal()
283
    sigImageDepthInitialized = QtCore.Signal()
284
285
    signal_history_event = QtCore.Signal()
286
287
    def __init__(self, config, **kwargs):
288
        super().__init__(config=config, **kwargs)
289
290
        #locking for thread safety
291
        self.threadlock = Mutex()
292
293
        # counter for scan_image
294
        self._scan_counter = 0
295
        self._zscan = False
296
        self.stopRequested = False
297
        self.depth_scan_dir_is_xz = True
298
        self.depth_img_is_xz = True
299
        self.permanent_scan = False
300
301
    def on_activate(self):
302
        """ Initialisation performed during activation of the module.
303
        """
304
        self._scanning_device = self.confocalscanner1()
305
        self._save_logic = self.savelogic()
306
307
        # Reads in the maximal scanning range. The unit of that scan range is micrometer!
308
        self.x_range = self._scanning_device.get_position_range()[0]
309
        self.y_range = self._scanning_device.get_position_range()[1]
310
        self.z_range = self._scanning_device.get_position_range()[2]
311
312
        # restore here ...
313
        self.history = []
314
        for i in reversed(range(1, self.max_history_length)):
315
            try:
316
                new_history_item = ConfocalHistoryEntry(self)
317
                new_history_item.deserialize(
318
                    self._statusVariables['history_{0}'.format(i)])
319
                self.history.append(new_history_item)
320
            except KeyError:
321
                pass
322
            except OldConfigFileError:
323
                self.log.warning(
324
                    'Old style config file detected. History {0} ignored.'.format(i))
325
            except:
326
                self.log.warning(
327
                        'Restoring history {0} failed.'.format(i))
328
        try:
329
            new_state = ConfocalHistoryEntry(self)
330
            new_state.deserialize(self._statusVariables['history_0'])
331
            new_state.restore(self)
332
        except:
333
            new_state = ConfocalHistoryEntry(self)
334
            new_state.restore(self)
335
        finally:
336
            self.history.append(new_state)
337
338
        self.history_index = len(self.history) - 1
339
340
        # Sets connections between signals and functions
341
        self.signal_scan_lines_next.connect(self._scan_line, QtCore.Qt.QueuedConnection)
342
        self.signal_start_scanning.connect(self.start_scanner, QtCore.Qt.QueuedConnection)
343
        self.signal_continue_scanning.connect(self.continue_scanner, QtCore.Qt.QueuedConnection)
344
345
        self._change_position('activation')
346
347
    def on_deactivate(self):
348
        """ Reverse steps of activation
349
350
        @return int: error code (0:OK, -1:error)
351
        """
352
        closing_state = ConfocalHistoryEntry(self)
353
        closing_state.snapshot(self)
354
        self.history.append(closing_state)
355
        histindex = 0
356
        for state in reversed(self.history):
357
            self._statusVariables['history_{0}'.format(histindex)] = state.serialize()
358
            histindex += 1
359
        return 0
360
361
    def switch_hardware(self, to_on=False):
362
        """ Switches the Hardware off or on.
363
364
        @param to_on: True switches on, False switched off
365
366
        @return int: error code (0:OK, -1:error)
367
        """
368
        if to_on:
369
            return self._scanning_device.activation()
370
        else:
371
            return self._scanning_device.reset_hardware()
372
373
    def set_clock_frequency(self, clock_frequency):
374
        """Sets the frequency of the clock
375
376
        @param int clock_frequency: desired frequency of the clock
377
378
        @return int: error code (0:OK, -1:error)
379
        """
380
        self._clock_frequency = int(clock_frequency)
381
        #checks if scanner is still running
382
        if self.module_state() == 'locked':
383
            return -1
384
        else:
385
            return 0
386
387
    def start_scanning(self, zscan = False, tag='logic'):
388
        """Starts scanning
389
390
        @param bool zscan: zscan if true, xyscan if false
391
392
        @return int: error code (0:OK, -1:error)
393
        """
394
        # TODO: this is dirty, but it works for now
395
#        while self.module_state() == 'locked':
396
#            time.sleep(0.01)
397
        self._scan_counter = 0
398
        self._zscan = zscan
399
        if self._zscan:
400
            self._zscan_continuable = True
401
        else:
402
            self._xyscan_continuable = True
403
404
        self.signal_start_scanning.emit(tag)
405
        return 0
406
407
    def continue_scanning(self,zscan,tag='logic'):
408
        """Continue scanning
409
410
        @return int: error code (0:OK, -1:error)
411
        """
412
        self._zscan = zscan
413
        if zscan:
414
            self._scan_counter = self._depth_line_pos
415
        else:
416
            self._scan_counter = self._xy_line_pos
417
        self.signal_continue_scanning.emit(tag)
418
        return 0
419
420
    def stop_scanning(self):
421
        """Stops the scan
422
423
        @return int: error code (0:OK, -1:error)
424
        """
425
        with self.threadlock:
426
            if self.module_state() == 'locked':
427
                self.stopRequested = True
428
        self.signal_stop_scanning.emit()
429
        return 0
430
431
    def initialize_image(self):
432
        """Initalization of the image.
433
434
        @return int: error code (0:OK, -1:error)
435
        """
436
        # x1: x-start-value, x2: x-end-value
437
        x1, x2 = self.image_x_range[0], self.image_x_range[1]
438
        # y1: x-start-value, y2: x-end-value
439
        y1, y2 = self.image_y_range[0], self.image_y_range[1]
440
        # z1: x-start-value, z2: x-end-value
441
        z1, z2 = self.image_z_range[0], self.image_z_range[1]
442
443
        # Checks if the x-start and x-end value are ok
444
        if x2 < x1:
445
            self.log.error(
446
                'x1 must be smaller than x2, but they are '
447
                '({0:.3f},{1:.3f}).'.format(x1, x2))
448
            return -1
449
450
        if self._zscan:
451
            if self.depth_img_is_xz:
452
                # creates an array of evenly spaced numbers over the interval
453
                # x1, x2 and the spacing is equal to xy_resolution
454
                self._X = np.linspace(x1, x2, self.xy_resolution)
455
            else:
456
                self._Y = np.linspace(y1, y2, self.xy_resolution)
457
458
            # Checks if the z-start and z-end value are ok
459
            if z2 < z1:
460
                self.log.error(
461
                    'z1 must be smaller than z2, but they are '
462
                    '({0:.3f},{1:.3f}).'.format(z1, z2))
463
                return -1
464
            # creates an array of evenly spaced numbers over the interval
465
            # z1, z2 and the spacing is equal to z_resolution
466
            self._Z = np.linspace(z1, z2, max(self.z_resolution, 2))
467
        else:
468
            # Checks if the y-start and y-end value are ok
469
            if y2 < y1:
470
                self.log.error(
471
                    'y1 must be smaller than y2, but they are '
472
                    '({0:.3f},{1:.3f}).'.format(y1, y2))
473
                return -1
474
475
            # prevents distorion of the image
476
            if (x2 - x1) >= (y2 - y1):
477
                self._X = np.linspace(x1, x2, max(self.xy_resolution, 2))
478
                self._Y = np.linspace(y1, y2, max(int(self.xy_resolution*(y2-y1)/(x2-x1)), 2))
479
            else:
480
                self._Y = np.linspace(y1, y2, max(self.xy_resolution, 2))
481
                self._X = np.linspace(x1, x2, max(int(self.xy_resolution*(x2-x1)/(y2-y1)), 2))
482
483
        self._XL = self._X
484
        self._YL = self._Y
485
        self._AL = np.zeros(self._XL.shape)
486
487
        # Arrays for retrace line
488
        self._return_XL = np.linspace(self._XL[-1], self._XL[0], self.return_slowness)
489
        self._return_AL = np.zeros(self._return_XL.shape)
490
491
        if self._zscan:
492
            self._image_vert_axis = self._Z
493
            # update image scan direction from setting
494
            self.depth_img_is_xz = self.depth_scan_dir_is_xz
495
            # depth scan is in xz plane
496
            if self.depth_img_is_xz:
497
                #self._image_horz_axis = self._X
498
                # creates an image where each pixel will be [x,y,z,counts]
499
                self.depth_image = np.zeros((
500
                        len(self._image_vert_axis),
501
                        len(self._X),
502
                        3 + len(self.get_scanner_count_channels())
503
                    ))
504
505
                self.depth_image[:, :, 0] = np.full(
506
                    (len(self._image_vert_axis), len(self._X)), self._XL)
507
508
                self.depth_image[:, :, 1] = self._current_y * np.ones(
509
                    (len(self._image_vert_axis), len(self._X)))
510
511
                z_value_matrix = np.full((len(self._X), len(self._image_vert_axis)), self._Z)
512
                self.depth_image[:, :, 2] = z_value_matrix.transpose()
513
514
            # depth scan is yz plane instead of xz plane
515
            else:
516
                #self._image_horz_axis = self._Y
517
                # creats an image where each pixel will be [x,y,z,counts]
518
                self.depth_image = np.zeros((
519
                        len(self._image_vert_axis),
520
                        len(self._Y),
521
                        3 + len(self.get_scanner_count_channels())
522
                    ))
523
524
                self.depth_image[:, :, 0] = self._current_x * np.ones(
525
                    (len(self._image_vert_axis), len(self._Y)))
526
527
                self.depth_image[:, :, 1] = np.full(
528
                    (len(self._image_vert_axis), len(self._Y)), self._YL)
529
530
                z_value_matrix = np.full((len(self._Y), len(self._image_vert_axis)), self._Z)
531
                self.depth_image[:, :, 2] = z_value_matrix.transpose()
532
533
                # now we are scanning along the y-axis, so we need a new return line along Y:
534
                self._return_YL = np.linspace(self._YL[-1], self._YL[0], self.return_slowness)
535
                self._return_AL = np.zeros(self._return_YL.shape)
536
537
            self.sigImageDepthInitialized.emit()
538
539
        # xy scan is in xy plane
540
        else:
541
            #self._image_horz_axis = self._X
542
            self._image_vert_axis = self._Y
543
            # creats an image where each pixel will be [x,y,z,counts]
544
            self.xy_image = np.zeros((
545
                    len(self._image_vert_axis),
546
                    len(self._X),
547
                    3 + len(self.get_scanner_count_channels())
548
                ))
549
550
            self.xy_image[:, :, 0] = np.full(
551
                (len(self._image_vert_axis), len(self._X)), self._XL)
552
553
            y_value_matrix = np.full((len(self._X), len(self._image_vert_axis)), self._Y)
554
            self.xy_image[:, :, 1] = y_value_matrix.transpose()
555
556
            self.xy_image[:, :, 2] = self._current_z * np.ones(
557
                (len(self._image_vert_axis), len(self._X)))
558
559
            self.sigImageXYInitialized.emit()
560
        return 0
561
562
    def start_scanner(self):
563
        """Setting up the scanner device and starts the scanning procedure
564
565
        @return int: error code (0:OK, -1:error)
566
        """
567
        self.module_state.lock()
568
569
        self._scanning_device.module_state.lock()
570
        if self.initialize_image() < 0:
571
            self._scanning_device.module_state.unlock()
572
            self.module_state.unlock()
573
            return -1
574
575
        clock_status = self._scanning_device.set_up_scanner_clock(
576
            clock_frequency=self._clock_frequency)
577
578
        if clock_status < 0:
579
            self._scanning_device.module_state.unlock()
580
            self.module_state.unlock()
581
            self.set_position('scanner')
582
            return -1
583
584
        scanner_status = self._scanning_device.set_up_scanner()
585
586
        if scanner_status < 0:
587
            self._scanning_device.close_scanner_clock()
588
            self._scanning_device.module_state.unlock()
589
            self.module_state.unlock()
590
            self.set_position('scanner')
591
            return -1
592 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
593
        self.signal_scan_lines_next.emit()
594
        return 0
595
596
    def continue_scanner(self):
597
        """Continue the scanning procedure
598
599
        @return int: error code (0:OK, -1:error)
600
        """
601
        self.module_state.lock()
602
        self._scanning_device.module_state.lock()
603
604
        clock_status = self._scanning_device.set_up_scanner_clock(
605
            clock_frequency=self._clock_frequency)
606
607
        if clock_status < 0:
608
            self._scanning_device.module_state.unlock()
609
            self.module_state.unlock()
610
            self.set_position('scanner')
611
            return -1
612
613
        scanner_status = self._scanning_device.set_up_scanner()
614
615
        if scanner_status < 0:
616
            self._scanning_device.close_scanner_clock()
617
            self._scanning_device.module_state.unlock()
618
            self.module_state.unlock()
619
            self.set_position('scanner')
620
            return -1
621 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
622
        self.signal_scan_lines_next.emit()
623
        return 0
624
625
    def kill_scanner(self):
626
        """Closing the scanner device.
627
628
        @return int: error code (0:OK, -1:error)
629
        """
630
        try:
631
            self._scanning_device.close_scanner()
632
        except Exception as e:
633
            self.log.exception('Could not close the scanner.')
634
        try:
635
            self._scanning_device.close_scanner_clock()
636
        except Exception as e:
637
            self.log.exception('Could not close the scanner clock.')
638
        try:
639
            self._scanning_device.module_state.unlock()
640
        except Exception as e:
641
            self.log.exception('Could not unlock scanning device.')
642
643
        return 0
644
645
    def set_position(self, tag, x=None, y=None, z=None, a=None):
646
        """Forwarding the desired new position from the GUI to the scanning device.
647
648
        @param string tag: TODO
649
650
        @param float x: if defined, changes to postion in x-direction (microns)
651
        @param float y: if defined, changes to postion in y-direction (microns)
652
        @param float z: if defined, changes to postion in z-direction (microns)
653
        @param float a: if defined, changes to postion in a-direction (microns)
654
655
        @return int: error code (0:OK, -1:error)
656
        """
657
        # Changes the respective value
658
        if x is not None:
659
            self._current_x = x
660
        if y is not None:
661
            self._current_y = y
662
        if z is not None:
663
            self._current_z = z
664
        if a is not None:
665
            self._current_a = a
666
667
        # Checks if the scanner is still running
668
        if self.module_state() == 'locked' or self._scanning_device.module_state() == 'locked':
669
            return -1
670
        else:
671
            self._change_position(tag)
672
            self.signal_change_position.emit(tag)
673
            return 0
674
675
    def _change_position(self, tag):
676
        """ Threaded method to change the hardware position.
677
678
        @return int: error code (0:OK, -1:error)
679
        """
680
        ch_array = ['x', 'y', 'z', 'a']
681
        pos_array = [self._current_x, self._current_y, self._current_z, self._current_a]
682
        pos_dict = {}
683
684
        for i, ch in enumerate(self.get_scanner_axes()):
685
            pos_dict[ch_array[i]] = pos_array[i]
686
687
        self._scanning_device.scanner_set_position(**pos_dict)
688
        return 0
689
690
    def get_position(self):
691
        """ Get position from scanning device.
692
693
        @return list: with three entries x, y and z denoting the current
694
                      position in meters
695
        """
696
        return self._scanning_device.get_scanner_position()
697
698
    def get_scanner_axes(self):
699
        """ Get axes from scanning device.
700
          @return list(str): names of scanner axes
701
        """
702
        return self._scanning_device.get_scanner_axes()
703
704
    def get_scanner_count_channels(self):
705
        """ Get lis of counting channels from scanning device.
706
          @return list(str): names of counter channels
707
        """
708
        return self._scanning_device.get_scanner_count_channels()
709
710
    def _scan_line(self):
711
        """scanning an image in either depth or xy
712
713
        """
714
        # stops scanning
715
        if self.stopRequested:
716
            with self.threadlock:
717
                self.kill_scanner()
718
                self.stopRequested = False
719
                self.module_state.unlock()
720
                self.signal_xy_image_updated.emit()
721
                self.signal_depth_image_updated.emit()
722
                self.set_position('scanner')
723
                if self._zscan:
724
                    self._depth_line_pos = self._scan_counter
725
                else:
726
                    self._xy_line_pos = self._scan_counter
727
                # add new history entry
728
                new_history = ConfocalHistoryEntry(self)
729
                new_history.snapshot(self)
730
                self.history.append(new_history)
731
                if len(self.history) > self.max_history_length:
732
                    self.history.pop(0)
733
                self.history_index = len(self.history) - 1
734
                return
735
736
        image = self.depth_image if self._zscan else self.xy_image
737
        n_ch = len(self.get_scanner_axes())
738
        s_ch = len(self.get_scanner_count_channels())
739
740
        try:
741
            if self._scan_counter == 0:
742
                # make a line from the current cursor position to
743
                # the starting position of the first scan line of the scan
744
                rs = self.return_slowness
745
                lsx = np.linspace(self._current_x, image[self._scan_counter, 0, 0], rs)
746
                lsy = np.linspace(self._current_y, image[self._scan_counter, 0, 1], rs)
747
                lsz = np.linspace(self._current_z, image[self._scan_counter, 0, 2], rs)
748
                if n_ch <= 3:
749
                    start_line = np.vstack([lsx, lsy, lsz][0:n_ch])
750
                else:
751
                    start_line = np.vstack(
752
                        [lsx, lsy, lsz, np.ones(lsx.shape) * self._current_a])
753
                # move to the start position of the scan, counts are thrown away
754
                start_line_counts = self._scanning_device.scan_line(start_line)
755
                if np.any(start_line_counts == -1):
756
                    self.stopRequested = True
757
                    self.signal_scan_lines_next.emit()
758
                    return
759
760
            # adjust z of line in image to current z before building the line
761
            if not self._zscan:
762
                z_shape = image[self._scan_counter, :, 2].shape
763
                image[self._scan_counter, :, 2] = self._current_z * np.ones(z_shape)
764
765
            # make a line in the scan, _scan_counter says which one it is
766
            lsx = image[self._scan_counter, :, 0]
767
            lsy = image[self._scan_counter, :, 1]
768
            lsz = image[self._scan_counter, :, 2]
769
            if n_ch <= 3:
770
                line = np.vstack([lsx, lsy, lsz][0:n_ch])
771
            else:
772
                line = np.vstack(
773
                    [lsx, lsy, lsz, np.ones(lsx.shape) * self._current_a])
774
775
            # scan the line in the scan
776
            line_counts = self._scanning_device.scan_line(line, pixel_clock=True)
777
            if np.any(line_counts == -1):
778
                self.stopRequested = True
779
                self.signal_scan_lines_next.emit()
780 View Code Duplication
                return
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
781
782
            # make a line to go to the starting position of the next scan line
783
            if self.depth_img_is_xz or not self._zscan:
784
                if n_ch <= 3:
785
                    return_line = np.vstack([
786
                        self._return_XL,
787
                        image[self._scan_counter, 0, 1] * np.ones(self._return_XL.shape),
788
                        image[self._scan_counter, 0, 2] * np.ones(self._return_XL.shape)
789
                    ][0:n_ch])
790
                else:
791
                    return_line = np.vstack([
792
                            self._return_XL,
793
                            image[self._scan_counter, 0, 1] * np.ones(self._return_XL.shape),
794 View Code Duplication
                            image[self._scan_counter, 0, 2] * np.ones(self._return_XL.shape),
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
795
                            np.ones(self._return_XL.shape) * self._current_a
796
                        ])
797
            else:
798
                if n_ch <= 3:
799
                    return_line = np.vstack([
800
                            image[self._scan_counter, 0, 1] * np.ones(self._return_YL.shape),
801
                            self._return_YL,
802
                            image[self._scan_counter, 0, 2] * np.ones(self._return_YL.shape)
803
                        ][0:n_ch])
804
                else:
805
                    return_line = np.vstack([
806
                            image[self._scan_counter, 0, 1] * np.ones(self._return_YL.shape),
807
                            self._return_YL,
808
                            image[self._scan_counter, 0, 2] * np.ones(self._return_YL.shape),
809
                            np.ones(self._return_YL.shape) * self._current_a
810
                        ])
811
812
            # return the scanner to the start of next line, counts are thrown away
813
            return_line_counts = self._scanning_device.scan_line(return_line)
814
            if np.any(return_line_counts == -1):
815
                self.stopRequested = True
816
                self.signal_scan_lines_next.emit()
817
                return
818
819
            # update image with counts from the line we just scanned
820
            if self._zscan:
821
                if self.depth_img_is_xz:
822
                    self.depth_image[self._scan_counter, :, 3:3 + s_ch] = line_counts
823
                else:
824
                    self.depth_image[self._scan_counter, :, 3:3 + s_ch] = line_counts
825
                self.signal_depth_image_updated.emit()
826
            else:
827
                self.xy_image[self._scan_counter, :, 3:3 + s_ch] = line_counts
828
                self.signal_xy_image_updated.emit()
829
830
            # next line in scan
831
            self._scan_counter += 1
832
833
            # stop scanning when last line scan was performed and makes scan not continuable
834
            if self._scan_counter >= np.size(self._image_vert_axis):
835
                if not self.permanent_scan:
836
                    self.stop_scanning()
837
                    if self._zscan:
838
                        self._zscan_continuable = False
839
                    else:
840
                        self._xyscan_continuable = False
841
                else:
842
                    self._scan_counter = 0
843
844
            self.signal_scan_lines_next.emit()
845
        except:
846
            self.log.exception('The scan went wrong, killing the scanner.')
847
            self.stop_scanning()
848
            self.signal_scan_lines_next.emit()
849
850
    def save_xy_data(self, colorscale_range=None, percentile_range=None):
851
        """ Save the current confocal xy data to file.
852
853
        Two files are created.  The first is the imagedata, which has a text-matrix of count values
854
        corresponding to the pixel matrix of the image.  Only count-values are saved here.
855
856
        The second file saves the full raw data with x, y, z, and counts at every pixel.
857
858
        A figure is also saved.
859
860
        @param: list colorscale_range (optional) The range [min, max] of the display colour scale (for the figure)
861
862
        @param: list percentile_range (optional) The percentile range [min, max] of the color scale
863
        """
864
        filepath = self._save_logic.get_path_for_module('Confocal')
865
        timestamp = datetime.datetime.now()
866
        # Prepare the metadata parameters (common to both saved files):
867
        parameters = OrderedDict()
868
869
        parameters['X image min (m)'] = self.image_x_range[0]
870
        parameters['X image max (m)'] = self.image_x_range[1]
871
        parameters['X image range (m)'] = self.image_x_range[1] - self.image_x_range[0]
872
873
        parameters['Y image min'] = self.image_y_range[0]
874
        parameters['Y image max'] = self.image_y_range[1]
875
        parameters['Y image range'] = self.image_y_range[1] - self.image_y_range[0]
876
877
        parameters['XY resolution (samples per range)'] = self.xy_resolution
878
        parameters['XY Image at z position (m)'] = self._current_z
879
880
        parameters['Clock frequency of scanner (Hz)'] = self._clock_frequency
881
        parameters['Return Slowness (Steps during retrace line)'] = self.return_slowness
882
883
        # Prepare a figure to be saved
884
        figure_data = self.xy_image[:, :, 3]
885
        image_extent = [self.image_x_range[0],
886
                        self.image_x_range[1],
887
                        self.image_y_range[0],
888
                        self.image_y_range[1]]
889
        axes = ['X', 'Y']
890
        crosshair_pos = [self.get_position()[0], self.get_position()[1]]
891
892
        figs = {ch: self.draw_figure(data=self.xy_image[:, :, 3 + n],
893
                                     image_extent=image_extent,
894
                                     scan_axis=axes,
895
                                     cbar_range=colorscale_range,
896
                                     percentile_range=percentile_range,
897
                                     crosshair_pos=crosshair_pos)
898
                for n, ch in enumerate(self.get_scanner_count_channels())}
899
900
        # Save the image data and figure
901
        for n, ch in enumerate(self.get_scanner_count_channels()):
902
            # data for the text-array "image":
903
            image_data = OrderedDict()
904
            image_data['Confocal pure XY scan image data without axis.\n'
905
                'The upper left entry represents the signal at the upper left pixel position.\n'
906
                'A pixel-line in the image corresponds to a row '
907
                'of entries where the Signal is in counts/s:'] = self.xy_image[:, :, 3 + n]
908
909
            filelabel = 'confocal_xy_image_{0}'.format(ch.replace('/', ''))
910
            self._save_logic.save_data(image_data,
911
                                       filepath=filepath,
912
                                       timestamp=timestamp,
913
                                       parameters=parameters,
914
                                       filelabel=filelabel,
915
                                       fmt='%.6e',
916
                                       delimiter='\t',
917
                                       plotfig=figs[ch])
918
919
        # prepare the full raw data in an OrderedDict:
920
        data = OrderedDict()
921
        data['x position (m)'] = self.xy_image[:, :, 0].flatten()
922
        data['y position (m)'] = self.xy_image[:, :, 1].flatten()
923
        data['z position (m)'] = self.xy_image[:, :, 2].flatten()
924
925
        for n, ch in enumerate(self.get_scanner_count_channels()):
926
            data['count rate {0} (Hz)'.format(ch)] = self.xy_image[:, :, 3 + n].flatten()
927
928
        # Save the raw data to file
929
        filelabel = 'confocal_xy_data'
930
        self._save_logic.save_data(data,
931
                                   filepath=filepath,
932
                                   timestamp=timestamp,
933
                                   parameters=parameters,
934
                                   filelabel=filelabel,
935
                                   fmt='%.6e',
936
                                   delimiter='\t')
937
938
        self.log.debug('Confocal Image saved.')
939
        self.signal_xy_data_saved.emit()
940
        return
941
942
    def save_depth_data(self, colorscale_range=None, percentile_range=None):
943
        """ Save the current confocal depth data to file.
944
945
        Two files are created.  The first is the imagedata, which has a text-matrix of count values
946
        corresponding to the pixel matrix of the image.  Only count-values are saved here.
947
948
        The second file saves the full raw data with x, y, z, and counts at every pixel.
949
        """
950
        filepath = self._save_logic.get_path_for_module('Confocal')
951
        timestamp = datetime.datetime.now()
952
        # Prepare the metadata parameters (common to both saved files):
953
        parameters = OrderedDict()
954
955
        # TODO: This needs to check whether the scan was XZ or YZ direction
956
        parameters['X image min (m)'] = self.image_x_range[0]
957
        parameters['X image max (m)'] = self.image_x_range[1]
958
        parameters['X image range (m)'] = self.image_x_range[1] - self.image_x_range[0]
959
960
        parameters['Z image min'] = self.image_z_range[0]
961
        parameters['Z image max'] = self.image_z_range[1]
962
        parameters['Z image range'] = self.image_z_range[1] - self.image_z_range[0]
963
964
        parameters['XY resolution (samples per range)'] = self.xy_resolution
965
        parameters['Z resolution (samples per range)'] = self.z_resolution
966
        parameters['Depth Image at y position (m)'] = self._current_y
967
968
        parameters['Clock frequency of scanner (Hz)'] = self._clock_frequency
969
        parameters['Return Slowness (Steps during retrace line)'] = self.return_slowness
970
971
        if self.depth_img_is_xz:
972
            horizontal_range = [self.image_x_range[0], self.image_x_range[1]]
973
            axes = ['X', 'Z']
974
            crosshair_pos = [self.get_position()[0], self.get_position()[2]]
975
        else:
976
            horizontal_range = [self.image_y_range[0], self.image_y_range[1]]
977
            axes = ['Y', 'Z']
978
            crosshair_pos = [self.get_position()[1], self.get_position()[2]]
979
980
        image_extent = [horizontal_range[0],
981
                        horizontal_range[1],
982
                        self.image_z_range[0],
983
                        self.image_z_range[1]]
984
985
        figs = {ch: self.draw_figure(data=self.depth_image[:, :, 3 + n],
986
                                     image_extent=image_extent,
987
                                     scan_axis=axes,
988
                                     cbar_range=colorscale_range,
989
                                     percentile_range=percentile_range,
990
                                     crosshair_pos=crosshair_pos)
991
                for n, ch in enumerate(self.get_scanner_count_channels())}
992
993
        # Save the image data and figure
994
        for n, ch in enumerate(self.get_scanner_count_channels()):
995
            # data for the text-array "image":
996
            image_data = OrderedDict()
997
            image_data['Confocal pure depth scan image data without axis.\n'
998
                'The upper left entry represents the signal at the upper left pixel position.\n'
999
                'A pixel-line in the image corresponds to a row in '
1000
                'of entries where the Signal is in counts/s:'] = self.depth_image[:, :, 3 + n]
1001
1002
            filelabel = 'confocal_depth_image_{0}'.format(ch.replace('/', ''))
1003
            self._save_logic.save_data(image_data,
1004
                                       filepath=filepath,
1005
                                       timestamp=timestamp,
1006
                                       parameters=parameters,
1007
                                       filelabel=filelabel,
1008
                                       fmt='%.6e',
1009
                                       delimiter='\t',
1010
                                       plotfig=figs[ch])
1011
1012
        # prepare the full raw data in an OrderedDict:
1013
        data = OrderedDict()
1014
        data['x position (m)'] = self.depth_image[:, :, 0].flatten()
1015
        data['y position (m)'] = self.depth_image[:, :, 1].flatten()
1016
        data['z position (m)'] = self.depth_image[:, :, 2].flatten()
1017
1018
        for n, ch in enumerate(self.get_scanner_count_channels()):
1019
            data['count rate {0} (Hz)'.format(ch)] = self.depth_image[:, :, 3 + n].flatten()
1020
1021
        # Save the raw data to file
1022
        filelabel = 'confocal_depth_data'
1023
        self._save_logic.save_data(data,
1024
                                   filepath=filepath,
1025
                                   timestamp=timestamp,
1026
                                   parameters=parameters,
1027
                                   filelabel=filelabel,
1028
                                   fmt='%.6e',
1029
                                   delimiter='\t')
1030
1031
        self.log.debug('Confocal Image saved.')
1032
        self.signal_depth_data_saved.emit()
1033
        return
1034
1035
    def draw_figure(self, data, image_extent, scan_axis=None, cbar_range=None, percentile_range=None,  crosshair_pos=None):
1036
        """ Create a 2-D color map figure of the scan image.
1037
1038
        @param: array data: The NxM array of count values from a scan with NxM pixels.
1039
1040
        @param: list image_extent: The scan range in the form [hor_min, hor_max, ver_min, ver_max]
1041
1042
        @param: list axes: Names of the horizontal and vertical axes in the image
1043
1044
        @param: list cbar_range: (optional) [color_scale_min, color_scale_max].  If not supplied then a default of
1045
                                 data_min to data_max will be used.
1046
1047
        @param: list percentile_range: (optional) Percentile range of the chosen cbar_range.
1048
1049
        @param: list crosshair_pos: (optional) crosshair position as [hor, vert] in the chosen image axes.
1050
1051
        @return: fig fig: a matplotlib figure object to be saved to file.
1052
        """
1053
        if scan_axis is None:
1054
            scan_axis = ['X', 'Y']
1055
1056
        # If no colorbar range was given, take full range of data
1057
        if cbar_range is None:
1058
            cbar_range = [np.min(data), np.max(data)]
1059
1060
        # Scale color values using SI prefix
1061
        prefix = ['', 'k', 'M', 'G']
1062
        prefix_count = 0
1063
        image_data = data
1064
        draw_cb_range = np.array(cbar_range)
1065
        image_dimension = image_extent.copy()
1066
1067
        while draw_cb_range[1] > 1000:
1068
            image_data = image_data/1000
1069
            draw_cb_range = draw_cb_range/1000
1070
            prefix_count = prefix_count + 1
1071
1072
        c_prefix = prefix[prefix_count]
1073
1074
1075
        # Scale axes values using SI prefix
1076
        axes_prefix = ['', 'm', r'$\mathrm{\mu}$', 'n']
1077
        x_prefix_count = 0
1078
        y_prefix_count = 0
1079
1080
        while np.abs(image_dimension[1]-image_dimension[0]) < 1:
1081
            image_dimension[0] = image_dimension[0] * 1000.
1082
            image_dimension[1] = image_dimension[1] * 1000.
1083
            x_prefix_count = x_prefix_count + 1
1084
1085
        while np.abs(image_dimension[3] - image_dimension[2]) < 1:
1086
            image_dimension[2] = image_dimension[2] * 1000.
1087
            image_dimension[3] = image_dimension[3] * 1000.
1088
            y_prefix_count = y_prefix_count + 1
1089
1090
        x_prefix = axes_prefix[x_prefix_count]
1091
        y_prefix = axes_prefix[y_prefix_count]
1092
1093
        # Use qudi style
1094
        plt.style.use(self._save_logic.mpl_qd_style)
1095
1096
        # Create figure
1097
        fig, ax = plt.subplots()
1098
1099
        # Create image plot
1100
        cfimage = ax.imshow(image_data,
1101
                            cmap=plt.get_cmap('inferno'), # reference the right place in qd
1102
                            origin="lower",
1103
                            vmin=draw_cb_range[0],
1104
                            vmax=draw_cb_range[1],
1105
                            interpolation='none',
1106
                            extent=image_dimension
1107
                            )
1108
1109
        ax.set_aspect(1)
1110
        ax.set_xlabel(scan_axis[0] + ' position (' + x_prefix + 'm)')
1111
        ax.set_ylabel(scan_axis[1] + ' position (' + y_prefix + 'm)')
1112
        ax.spines['bottom'].set_position(('outward', 10))
1113
        ax.spines['left'].set_position(('outward', 10))
1114
        ax.spines['top'].set_visible(False)
1115
        ax.spines['right'].set_visible(False)
1116
        ax.get_xaxis().tick_bottom()
1117
        ax.get_yaxis().tick_left()
1118
1119
        # draw the crosshair position if defined
1120
        if crosshair_pos is not None:
1121
            trans_xmark = mpl.transforms.blended_transform_factory(
1122
                ax.transData,
1123
                ax.transAxes)
1124
1125
            trans_ymark = mpl.transforms.blended_transform_factory(
1126
                ax.transAxes,
1127
                ax.transData)
1128
1129
            ax.annotate('', xy=(crosshair_pos[0]*np.power(1000,x_prefix_count), 0),
1130
                        xytext=(crosshair_pos[0]*np.power(1000,x_prefix_count), -0.01), xycoords=trans_xmark,
1131
                        arrowprops=dict(facecolor='#17becf', shrink=0.05),
1132
                        )
1133
1134
            ax.annotate('', xy=(0, crosshair_pos[1]*np.power(1000,y_prefix_count)),
1135
                        xytext=(-0.01, crosshair_pos[1]*np.power(1000,y_prefix_count)), xycoords=trans_ymark,
1136
                        arrowprops=dict(facecolor='#17becf', shrink=0.05),
1137
                        )
1138
1139
        # Draw the colorbar
1140
        cbar = plt.colorbar(cfimage, shrink=0.8)#, fraction=0.046, pad=0.08, shrink=0.75)
1141
        cbar.set_label('Fluorescence (' + c_prefix + 'c/s)')
1142
1143 View Code Duplication
        # remove ticks from colorbar for cleaner image
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1144
        cbar.ax.tick_params(which=u'both', length=0)
1145
1146
        # If we have percentile information, draw that to the figure
1147
        if percentile_range is not None:
1148
            cbar.ax.annotate(str(percentile_range[0]),
1149
                             xy=(-0.3, 0.0),
1150
                             xycoords='axes fraction',
1151
                             horizontalalignment='right',
1152
                             verticalalignment='center',
1153
                             rotation=90
1154
                             )
1155
            cbar.ax.annotate(str(percentile_range[1]),
1156
                             xy=(-0.3, 1.0),
1157
                             xycoords='axes fraction',
1158
                             horizontalalignment='right',
1159
                             verticalalignment='center',
1160
                             rotation=90
1161
                             )
1162
            cbar.ax.annotate('(percentile)',
1163
                             xy=(-0.3, 0.5),
1164
                             xycoords='axes fraction',
1165
                             horizontalalignment='right',
1166
                             verticalalignment='center',
1167
                             rotation=90
1168
                             )
1169
        self.signal_draw_figure_completed.emit()
1170
        return fig
1171
1172
    ##################################### Tilt correction ########################################
1173
1174
    @QtCore.Slot()
1175
    def set_tilt_point1(self):
1176
        """ Gets the first reference point for tilt correction."""
1177
        self.point1 = np.array(self._scanning_device.get_scanner_position()[:3])
1178
        self.signal_tilt_correction_update.emit()
1179
1180
    @QtCore.Slot()
1181
    def set_tilt_point2(self):
1182
        """ Gets the second reference point for tilt correction."""
1183
        self.point2 = np.array(self._scanning_device.get_scanner_position()[:3])
1184
        self.signal_tilt_correction_update.emit()
1185
1186
    @QtCore.Slot()
1187
    def set_tilt_point3(self):
1188
        """Gets the third reference point for tilt correction."""
1189
        self.point3 = np.array(self._scanning_device.get_scanner_position()[:3])
1190
        self.signal_tilt_correction_update.emit()
1191
1192
    @QtCore.Slot()
1193
    def calc_tilt_correction(self):
1194
        """ Calculates the values for the tilt correction. """
1195
        a = self.point2 - self.point1
1196
        b = self.point3 - self.point1
1197
        n = np.cross(a, b)
1198
        self._scanning_device.tilt_variable_ax = n[0] / n[2]
1199
        self._scanning_device.tilt_variable_ay = n[1] / n[2]
1200
1201
    @QtCore.Slot(bool)
1202
    def set_tilt_correction(self, enabled):
1203
        """ Set tilt correction in tilt interfuse.
1204
1205
            @param bool enabled: whether we want to use tilt correction
1206
        """
1207
        self._scanning_device.tiltcorrection = enabled
1208
        self._scanning_device.tilt_reference_x = self._scanning_device.get_scanner_position()[0]
1209
        self._scanning_device.tilt_reference_y = self._scanning_device.get_scanner_position()[1]
1210
        self.signal_tilt_correction_active.emit(enabled)
1211
1212
    def history_forward(self):
1213
        """ Move forward in confocal image history.
1214
        """
1215 View Code Duplication
        if self.history_index < len(self.history) - 1:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1216
            self.history_index += 1
1217
            self.history[self.history_index].restore(self)
1218
            self.signal_xy_image_updated.emit()
1219
            self.signal_depth_image_updated.emit()
1220
            self.signal_tilt_correction_update.emit()
1221
            self.signal_tilt_correction_active.emit(self._scanning_device.tiltcorrection)
1222
            self._change_position('history')
1223
            self.signal_change_position.emit('history')
1224
            self.signal_history_event.emit()
1225
1226
    def history_back(self):
1227
        """ Move backwards in confocal image history.
1228
        """
1229 View Code Duplication
        if self.history_index > 0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1230
            self.history_index -= 1
1231
            self.history[self.history_index].restore(self)
1232
            self.signal_xy_image_updated.emit()
1233
            self.signal_depth_image_updated.emit()
1234
            self.signal_tilt_correction_update.emit()
1235
            self.signal_tilt_correction_active.emit(self._scanning_device.tiltcorrection)
1236
            self._change_position('history')
1237
            self.signal_change_position.emit('history')
1238
            self.signal_history_event.emit()
1239