Completed
Pull Request — master (#375)
by
unknown
01:13
created

ConfocalLogic.on_activate()   B

Complexity

Conditions 6

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

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