Completed
Pull Request — master (#378)
by
unknown
04:12
created

ConfocalLogic._image_range_ok()   A

Complexity

Conditions 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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