microeye 2.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microEye/__init__.py +47 -0
- microEye/_version.py +2 -0
- microEye/analysis/__init__.py +1 -0
- microEye/analysis/checklist_dialog.py +143 -0
- microEye/analysis/cmosMaps.py +228 -0
- microEye/analysis/filters/__init__.py +9 -0
- microEye/analysis/filters/base.py +21 -0
- microEye/analysis/filters/spatial.py +338 -0
- microEye/analysis/filters/temporal.py +76 -0
- microEye/analysis/fitting/__init__.py +0 -0
- microEye/analysis/fitting/fit.py +680 -0
- microEye/analysis/fitting/nena.py +375 -0
- microEye/analysis/fitting/phasor_fit.py +90 -0
- microEye/analysis/fitting/processing.py +317 -0
- microEye/analysis/fitting/psf/__init__.py +6 -0
- microEye/analysis/fitting/psf/extract.py +1129 -0
- microEye/analysis/fitting/psf/rubost_mean.py +150 -0
- microEye/analysis/fitting/psf/spline.py +167 -0
- microEye/analysis/fitting/psf/stats/__init__.py +12 -0
- microEye/analysis/fitting/psf/stats/core.py +295 -0
- microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
- microEye/analysis/fitting/psf/stats/io.py +104 -0
- microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
- microEye/analysis/fitting/psf/temp.py +147 -0
- microEye/analysis/fitting/psf/test.py +47 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
- microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
- microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
- microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
- microEye/analysis/fitting/results.py +917 -0
- microEye/analysis/fitting/results_stats.py +251 -0
- microEye/analysis/fitting/tardis.py +209 -0
- microEye/analysis/multi_viewer.py +568 -0
- microEye/analysis/processing/__init__.py +1 -0
- microEye/analysis/processing/frc.py +120 -0
- microEye/analysis/rendering/__init__.py +3 -0
- microEye/analysis/rendering/base.py +452 -0
- microEye/analysis/rendering/cloud.py +215 -0
- microEye/analysis/rendering/core.py +47 -0
- microEye/analysis/rendering/volumetric.py +198 -0
- microEye/analysis/tools/__init__.py +0 -0
- microEye/analysis/tools/kymograms.py +1079 -0
- microEye/analysis/tools/roi_selectors.py +556 -0
- microEye/analysis/utils/__init__.py +3 -0
- microEye/analysis/utils/coordinates.py +18 -0
- microEye/analysis/utils/images.py +68 -0
- microEye/analysis/utils/windows.py +19 -0
- microEye/analysis/viewer/__init__.py +3 -0
- microEye/analysis/viewer/image_options_widget.py +615 -0
- microEye/analysis/viewer/images.py +1225 -0
- microEye/analysis/viewer/layers_widget.py +415 -0
- microEye/analysis/viewer/localizations.py +1304 -0
- microEye/analysis/viewer/psf.py +1003 -0
- microEye/analysis/viewer/volume.py +456 -0
- microEye/hardware/__init__.py +3 -0
- microEye/hardware/cams/__init__.py +29 -0
- microEye/hardware/cams/camera_calibration.py +99 -0
- microEye/hardware/cams/camera_list.py +528 -0
- microEye/hardware/cams/camera_options.py +694 -0
- microEye/hardware/cams/camera_panel.py +941 -0
- microEye/hardware/cams/dummy/__init__.py +1 -0
- microEye/hardware/cams/dummy/dummy_panel.py +759 -0
- microEye/hardware/cams/jobs.py +497 -0
- microEye/hardware/cams/line_profiler.py +99 -0
- microEye/hardware/cams/linescan/IR_Cam.py +474 -0
- microEye/hardware/cams/linescan/__init__.py +1 -0
- microEye/hardware/cams/micam.py +524 -0
- microEye/hardware/cams/pco/__init__.py +58 -0
- microEye/hardware/cams/pco/enums.py +382 -0
- microEye/hardware/cams/pco/pco_cam.py +761 -0
- microEye/hardware/cams/pco/pco_panel.py +477 -0
- microEye/hardware/cams/shortcuts.py +312 -0
- microEye/hardware/cams/thorlabs/__init__.py +1 -0
- microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
- microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
- microEye/hardware/cams/ueye/__init__.py +1 -0
- microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
- microEye/hardware/cams/ueye/ueye_panel.py +861 -0
- microEye/hardware/cams/vimba/__init__.py +1 -0
- microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
- microEye/hardware/cams/vimba/vimba_panel.py +813 -0
- microEye/hardware/device.py +60 -0
- microEye/hardware/lasers/__init__.py +13 -0
- microEye/hardware/lasers/io_matchbox.py +791 -0
- microEye/hardware/lasers/io_params.py +85 -0
- microEye/hardware/lasers/io_single_laser.py +742 -0
- microEye/hardware/lasers/laser_relay.py +594 -0
- microEye/hardware/mieye/__init__.py +1 -0
- microEye/hardware/mieye/acquisition_manager.py +467 -0
- microEye/hardware/mieye/devices_manager.py +533 -0
- microEye/hardware/mieye/miEye.py +659 -0
- microEye/hardware/misc/__init__.py +0 -0
- microEye/hardware/misc/acquisition_view.py +71 -0
- microEye/hardware/misc/reglo.py +761 -0
- microEye/hardware/misc/temp.py +188 -0
- microEye/hardware/port_config.py +59 -0
- microEye/hardware/protocols/__init__.py +2 -0
- microEye/hardware/protocols/actions.py +402 -0
- microEye/hardware/protocols/actions_items.py +703 -0
- microEye/hardware/protocols/designer.py +244 -0
- microEye/hardware/protocols/scene_manager.py +191 -0
- microEye/hardware/protocols/serialization.py +97 -0
- microEye/hardware/pycromanager/__init__.py +16 -0
- microEye/hardware/pycromanager/core.py +1433 -0
- microEye/hardware/pycromanager/devices.py +461 -0
- microEye/hardware/pycromanager/enums.py +107 -0
- microEye/hardware/pycromanager/headless.py +153 -0
- microEye/hardware/pycromanager/utils.py +34 -0
- microEye/hardware/pycromanager/widgets/__init__.py +5 -0
- microEye/hardware/pycromanager/widgets/bridges.py +407 -0
- microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
- microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
- microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
- microEye/hardware/stages/__init__.py +18 -0
- microEye/hardware/stages/elliptec/__init__.py +5 -0
- microEye/hardware/stages/elliptec/baseDevice.py +314 -0
- microEye/hardware/stages/elliptec/device.py +384 -0
- microEye/hardware/stages/elliptec/deviceID.py +212 -0
- microEye/hardware/stages/elliptec/devicePort.py +379 -0
- microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
- microEye/hardware/stages/elliptec/devicesView.py +706 -0
- microEye/hardware/stages/elliptec/ellDevices.py +134 -0
- microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
- microEye/hardware/stages/elliptec/motorInfo.py +153 -0
- microEye/hardware/stages/elliptec/stage.py +62 -0
- microEye/hardware/stages/elliptec/test.py +139 -0
- microEye/hardware/stages/kinesis/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
- microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
- microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
- microEye/hardware/stages/kinesis/kinesis.py +776 -0
- microEye/hardware/stages/piezo_concept.py +607 -0
- microEye/hardware/stages/stabilizer.py +785 -0
- microEye/hardware/stages/stage.py +89 -0
- microEye/hardware/widgets/__init__.py +10 -0
- microEye/hardware/widgets/controller.py +246 -0
- microEye/hardware/widgets/devices.py +133 -0
- microEye/hardware/widgets/focusWidget.py +264 -0
- microEye/hardware/widgets/qlist_slider.py +113 -0
- microEye/hardware/widgets/scan_acquisition.py +424 -0
- microEye/icons/1024.png +0 -0
- microEye/icons/128.png +0 -0
- microEye/icons/16.png +0 -0
- microEye/icons/24.png +0 -0
- microEye/icons/256.png +0 -0
- microEye/icons/32.png +0 -0
- microEye/icons/48.png +0 -0
- microEye/icons/512.png +0 -0
- microEye/icons/64.png +0 -0
- microEye/icons/__init__.py +0 -0
- microEye/icons/close.svg +88 -0
- microEye/icons/mieye.png +0 -0
- microEye/icons/min.svg +83 -0
- microEye/icons/viewer.png +0 -0
- microEye/launcher.py +42 -0
- microEye/qt.py +181 -0
- microEye/utils/__init__.py +2 -0
- microEye/utils/enum_encoder.py +10 -0
- microEye/utils/expandable_groupbox.py +93 -0
- microEye/utils/gui_helper.py +457 -0
- microEye/utils/hid/__init__.py +8 -0
- microEye/utils/hid/controller.py +153 -0
- microEye/utils/hid/device.py +63 -0
- microEye/utils/hid/enums.py +140 -0
- microEye/utils/hid/utils.py +58 -0
- microEye/utils/labelled_slider.py +134 -0
- microEye/utils/metadata.py +599 -0
- microEye/utils/metadata_tree.py +718 -0
- microEye/utils/micro_launcher.py +245 -0
- microEye/utils/parameter_tree.py +325 -0
- microEye/utils/pyscripting.py +444 -0
- microEye/utils/retry_exec.py +35 -0
- microEye/utils/start_gui.py +112 -0
- microEye/utils/thread_worker.py +115 -0
- microEye/utils/uImage.py +1512 -0
- microeye-2.3.2.dist-info/METADATA +401 -0
- microeye-2.3.2.dist-info/RECORD +190 -0
- microeye-2.3.2.dist-info/WHEEL +5 -0
- microeye-2.3.2.dist-info/entry_points.txt +2 -0
- microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
- microeye-2.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import traceback
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from queue import Queue
|
|
9
|
+
|
|
10
|
+
import cv2
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pyqtgraph as pg
|
|
13
|
+
|
|
14
|
+
from microEye.hardware.cams.camera_options import CameraOptions, CamParams
|
|
15
|
+
from microEye.hardware.cams.jobs import AcquisitionJob
|
|
16
|
+
from microEye.hardware.cams.line_profiler import LineProfiler
|
|
17
|
+
from microEye.hardware.cams.micam import miCamera
|
|
18
|
+
from microEye.hardware.cams.shortcuts import (
|
|
19
|
+
CameraShortcutsWidget,
|
|
20
|
+
)
|
|
21
|
+
from microEye.qt import QDateTime, Qt, QtCore, QtWidgets, Signal, Slot
|
|
22
|
+
from microEye.utils.expandable_groupbox import ExpandableGroupBox
|
|
23
|
+
from microEye.utils.metadata_tree import MetadataEditorTree, MetaParams
|
|
24
|
+
from microEye.utils.thread_worker import QThreadWorker
|
|
25
|
+
from microEye.utils.uImage import uImage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Camera_Panel(QtWidgets.QGroupBox):
|
|
29
|
+
'''
|
|
30
|
+
A Qt Widget base class for controlling a camera | Inherits QGroupBox
|
|
31
|
+
'''
|
|
32
|
+
|
|
33
|
+
exposureChanged = Signal()
|
|
34
|
+
asyncFreerun = Signal(str, threading.Event)
|
|
35
|
+
updateStatsSignal = Signal(tuple)
|
|
36
|
+
updateRangeSignal = Signal(int, int)
|
|
37
|
+
|
|
38
|
+
def __init__(self, cam: miCamera, mini=False, *args, **kwargs):
|
|
39
|
+
'''
|
|
40
|
+
Initializes a new Camera_Panel Qt widget.
|
|
41
|
+
|
|
42
|
+
Inherits QGroupBox.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
cam : miCamera
|
|
47
|
+
Camera python adapter.
|
|
48
|
+
|
|
49
|
+
mini : bool, optional
|
|
50
|
+
Flag indicating if this is a mini camera panel, by default False.
|
|
51
|
+
|
|
52
|
+
Other Parameters
|
|
53
|
+
---------------
|
|
54
|
+
*args
|
|
55
|
+
Arguments to pass to the QGroupBox constructor.
|
|
56
|
+
|
|
57
|
+
**kwargs
|
|
58
|
+
Keyword arguments to pass to the QGroupBox constructor.
|
|
59
|
+
'''
|
|
60
|
+
super().__init__(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
|
63
|
+
|
|
64
|
+
self._cam = cam # miCamera
|
|
65
|
+
# flag true if master (always the first added camera is master)
|
|
66
|
+
self.master = False
|
|
67
|
+
|
|
68
|
+
self.c_worker = None # worker for capturing
|
|
69
|
+
self.d_worker = None # worker for display
|
|
70
|
+
self.s_worker = None # worker for saving
|
|
71
|
+
|
|
72
|
+
self.asyncFreerun.connect(
|
|
73
|
+
lambda prefix, event: self.start_free_run(Prefix=prefix, Event=event)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# number of bins for the histogram (4096 is set for 12bit mono-camera)
|
|
77
|
+
self._nBins = 2**12
|
|
78
|
+
self._hist = np.arange(0, self._nBins) * 0 # arrays for the Hist plot
|
|
79
|
+
self._bins = np.arange(0, self._nBins) # arrays for the Hist plot
|
|
80
|
+
|
|
81
|
+
self.__update_timer = time.time()
|
|
82
|
+
self.updateStatsSignal.connect(self.updateStats)
|
|
83
|
+
self.updateRangeSignal.connect(self.updateRange)
|
|
84
|
+
|
|
85
|
+
self._threadpool = (
|
|
86
|
+
QtCore.QThreadPool.globalInstance()
|
|
87
|
+
) # the threadpool for workers
|
|
88
|
+
|
|
89
|
+
# flag true to close camera adapter and dispose it
|
|
90
|
+
self._dispose_cam = False
|
|
91
|
+
self._buffer = Queue() # data memory stack
|
|
92
|
+
self._temps = Queue() # camera sensor temperature stack
|
|
93
|
+
# stack of (frame, temperature) tuples (for saving)
|
|
94
|
+
self.frame = None
|
|
95
|
+
self._frames = Queue()
|
|
96
|
+
# reserved for saving
|
|
97
|
+
home = os.path.expanduser('~')
|
|
98
|
+
|
|
99
|
+
self._directory = os.path.join(home, 'Desktop') # save directory
|
|
100
|
+
self._save_path = '' # save path
|
|
101
|
+
self._save_prefix = '' # save prefix
|
|
102
|
+
|
|
103
|
+
# acquisition job info class
|
|
104
|
+
self.acq_job = None
|
|
105
|
+
|
|
106
|
+
self.mini = mini
|
|
107
|
+
|
|
108
|
+
# Line Profiler
|
|
109
|
+
self.lineProfiler = LineProfiler()
|
|
110
|
+
|
|
111
|
+
# main layout
|
|
112
|
+
self.main_layout = QtWidgets.QVBoxLayout()
|
|
113
|
+
|
|
114
|
+
# set main layout
|
|
115
|
+
self.setLayout(self.main_layout)
|
|
116
|
+
|
|
117
|
+
# the main tab widget
|
|
118
|
+
self.main_tab_view = QtWidgets.QTabWidget()
|
|
119
|
+
self.main_layout.addWidget(self.main_tab_view)
|
|
120
|
+
|
|
121
|
+
# tab widgets
|
|
122
|
+
self.first_tab = QtWidgets.QWidget()
|
|
123
|
+
|
|
124
|
+
self.OME_tab = MetadataEditorTree()
|
|
125
|
+
|
|
126
|
+
# add tabs
|
|
127
|
+
self.main_tab_view.addTab(self.first_tab, 'Main')
|
|
128
|
+
if not self.mini:
|
|
129
|
+
self.main_tab_view.addTab(self.OME_tab, 'OME-XML metadata')
|
|
130
|
+
|
|
131
|
+
# first tab vertical layout
|
|
132
|
+
self.first_tab_Layout = QtWidgets.QFormLayout()
|
|
133
|
+
# set as first tab layout
|
|
134
|
+
self.first_tab.setLayout(self.first_tab_Layout)
|
|
135
|
+
|
|
136
|
+
self.camera_options = CameraOptions()
|
|
137
|
+
|
|
138
|
+
# Create the camera shortcuts widget
|
|
139
|
+
self.cam_shortcuts = CameraShortcutsWidget(
|
|
140
|
+
camera=self._cam,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def convinient_naming(mode: str):
|
|
144
|
+
name: str = self.camera_options.get_param_value(CamParams.EXPERIMENT_NAME)
|
|
145
|
+
|
|
146
|
+
if mode == 'exposure':
|
|
147
|
+
match = re.search(
|
|
148
|
+
r'(\d+)(?:_ms|ms)', name
|
|
149
|
+
) # find any number of format '000_ms' or '00ms'
|
|
150
|
+
if match:
|
|
151
|
+
exposure = self._cam.getExposure()
|
|
152
|
+
# update the exposure time in the name
|
|
153
|
+
name = name.replace(match.group(0), f'{exposure:03.0f}ms')
|
|
154
|
+
else:
|
|
155
|
+
name = f'{name}_{self._cam.getExposure():03.0f}ms'
|
|
156
|
+
|
|
157
|
+
elif mode == 'index':
|
|
158
|
+
# only from the start of the name
|
|
159
|
+
match = re.search(r'^\d+', name)
|
|
160
|
+
if match:
|
|
161
|
+
index = int(match.group())
|
|
162
|
+
index += 1
|
|
163
|
+
name = re.sub(r'^\d+', f'{index:03d}', name)
|
|
164
|
+
else:
|
|
165
|
+
name = f'001_{name}'
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
self.camera_options.set_param_value(CamParams.EXPERIMENT_NAME, name)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
self.cam_shortcuts.exposureChanged.connect(self.setExposure)
|
|
172
|
+
self.cam_shortcuts.autostretchChanged.connect(
|
|
173
|
+
self.camera_options.toggleAutostretch
|
|
174
|
+
)
|
|
175
|
+
self.cam_shortcuts.previewChanged.connect(self.camera_options.togglePreview)
|
|
176
|
+
self.cam_shortcuts.saveDataChanged.connect(self.camera_options.toggleSaveData)
|
|
177
|
+
self.cam_shortcuts.zoomChanged.connect(self.camera_options.setZoom)
|
|
178
|
+
self.cam_shortcuts.acquisitionStart.connect(self.start_free_run)
|
|
179
|
+
self.cam_shortcuts.acquisitionStop.connect(self.stop)
|
|
180
|
+
self.cam_shortcuts.adjustName.connect(convinient_naming)
|
|
181
|
+
|
|
182
|
+
# Create the context menu
|
|
183
|
+
menu = self.cam_shortcuts.create_context_menu(self.camera_options)
|
|
184
|
+
|
|
185
|
+
def show_shortcuts(pos):
|
|
186
|
+
self.cam_shortcuts.blockSignals(True)
|
|
187
|
+
self.cam_shortcuts.set_exposure(
|
|
188
|
+
self.camera_options.get_param_value(CamParams.EXPOSURE)
|
|
189
|
+
)
|
|
190
|
+
self.cam_shortcuts.set_zoom(
|
|
191
|
+
self.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
|
|
192
|
+
)
|
|
193
|
+
self.cam_shortcuts.set_autostretch(
|
|
194
|
+
self.camera_options.get_param_value(CamParams.AUTO_STRETCH)
|
|
195
|
+
)
|
|
196
|
+
self.cam_shortcuts.set_preview(
|
|
197
|
+
self.camera_options.get_param_value(CamParams.PREVIEW)
|
|
198
|
+
)
|
|
199
|
+
self.cam_shortcuts.set_save_data(
|
|
200
|
+
self.camera_options.get_param_value(CamParams.SAVE_DATA)
|
|
201
|
+
)
|
|
202
|
+
self.cam_shortcuts.blockSignals(False)
|
|
203
|
+
|
|
204
|
+
# display menu but centered horizontally
|
|
205
|
+
menu.exec(
|
|
206
|
+
self.main_tab_view.mapToGlobal(
|
|
207
|
+
QtCore.QPoint(self.main_tab_view.rect().left(), pos.y())
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self.camera_options.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
212
|
+
self.camera_options.customContextMenuRequested.connect(show_shortcuts)
|
|
213
|
+
|
|
214
|
+
self.first_tab_Layout.addRow(self.camera_options)
|
|
215
|
+
|
|
216
|
+
self.camera_options.get_param(
|
|
217
|
+
CamParams.EXPERIMENT_NAME
|
|
218
|
+
).sigValueChanged.connect(
|
|
219
|
+
lambda x: self.OME_tab.set_param_value(
|
|
220
|
+
MetaParams.EXPERIMENT_NAME, x.value()
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.save_dir_layout = QtWidgets.QHBoxLayout()
|
|
225
|
+
|
|
226
|
+
self.camera_options.set_param_value(CamParams.SAVE_DIRECTORY, self._directory)
|
|
227
|
+
self.camera_options.directoryChanged.connect(
|
|
228
|
+
lambda value: self.directory_changed(value)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
save_param = self.camera_options.get_param(CamParams.SAVE_DATA)
|
|
232
|
+
save_param.setOpts(enabled=not self.mini)
|
|
233
|
+
save_param.setValue(self.mini)
|
|
234
|
+
|
|
235
|
+
preview_param = self.camera_options.get_param(CamParams.PREVIEW)
|
|
236
|
+
preview_param.setValue(not self.mini)
|
|
237
|
+
preview_param.setOpts(enabled=not self.mini)
|
|
238
|
+
|
|
239
|
+
self.camera_options.get_param(CamParams.DISPLAY_STATS_OPTION).setOpts(
|
|
240
|
+
enabled=not self.mini
|
|
241
|
+
)
|
|
242
|
+
self.camera_options.get_param(CamParams.VIEW_OPTIONS).setOpts(
|
|
243
|
+
visible=not self.mini
|
|
244
|
+
)
|
|
245
|
+
self.camera_options.get_param(CamParams.RESIZE_DISPLAY).setOpts(
|
|
246
|
+
enabled=not self.mini
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
profiler_param = self.camera_options.get_param(CamParams.LINE_PROFILER)
|
|
250
|
+
profiler_param.setOpts(enabled=not self.mini)
|
|
251
|
+
profiler_param.sigStateChanged.connect(
|
|
252
|
+
lambda: self.lineProfiler.show()
|
|
253
|
+
if self.camera_options.get_param_value(CamParams.LINE_PROFILER)
|
|
254
|
+
else self.lineProfiler.hide()
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
self.camera_options.setROI.connect(lambda: self.set_ROI())
|
|
258
|
+
self.camera_options.resetROI.connect(lambda: self.reset_ROI())
|
|
259
|
+
self.camera_options.centerROI.connect(lambda: self.center_ROI())
|
|
260
|
+
self.camera_options.selectROI.connect(lambda: self.select_ROI())
|
|
261
|
+
self.camera_options.selectROIs.connect(lambda: self.select_ROIs())
|
|
262
|
+
self.camera_options.viewOptionChanged.connect(lambda: self.view_toggled())
|
|
263
|
+
|
|
264
|
+
# controls for histogram and cdf
|
|
265
|
+
self.histogram_group = ExpandableGroupBox('Histogram')
|
|
266
|
+
self.first_tab_Layout.addRow(self.histogram_group)
|
|
267
|
+
# Hist plotWidget
|
|
268
|
+
self.histogram = pg.PlotWidget()
|
|
269
|
+
self.hist_cdf = pg.PlotWidget()
|
|
270
|
+
greenP = pg.mkPen(color='g')
|
|
271
|
+
blueP = pg.mkPen(color='b')
|
|
272
|
+
greenB = pg.mkBrush(0, 255, 0, 32)
|
|
273
|
+
blueB = pg.mkBrush(0, 0, 255, 32)
|
|
274
|
+
self._plot_ref = self.histogram.plot(self._bins, self._hist, pen=greenP)
|
|
275
|
+
self._cdf_plot_ref = self.hist_cdf.plot(self._bins, self._hist, pen=greenP)
|
|
276
|
+
self._plot_ref_2 = self.histogram.plot(self._bins, self._hist, pen=blueP)
|
|
277
|
+
self._cdf_plot_ref_2 = self.hist_cdf.plot(self._bins, self._hist, pen=blueP)
|
|
278
|
+
|
|
279
|
+
self.lr_0 = pg.LinearRegionItem(
|
|
280
|
+
(0, self._nBins),
|
|
281
|
+
bounds=(0, self._nBins),
|
|
282
|
+
pen=greenP,
|
|
283
|
+
brush=greenB,
|
|
284
|
+
movable=True,
|
|
285
|
+
swapMode='push',
|
|
286
|
+
span=(0.0, 1),
|
|
287
|
+
)
|
|
288
|
+
self.lr_1 = pg.LinearRegionItem(
|
|
289
|
+
(0, self._nBins),
|
|
290
|
+
bounds=(0, self._nBins),
|
|
291
|
+
pen=blueP,
|
|
292
|
+
brush=blueB,
|
|
293
|
+
movable=True,
|
|
294
|
+
swapMode='push',
|
|
295
|
+
span=(1, 1),
|
|
296
|
+
)
|
|
297
|
+
self.histogram.addItem(self.lr_0)
|
|
298
|
+
self.histogram.addItem(self.lr_1)
|
|
299
|
+
|
|
300
|
+
self.histogram_group.layout().addWidget(self.histogram)
|
|
301
|
+
self.histogram_group.layout().addWidget(self.hist_cdf)
|
|
302
|
+
|
|
303
|
+
CameraOptions.combine_params(self.PARAMS)
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def cam(self):
|
|
307
|
+
'''The Camera property.
|
|
308
|
+
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
object
|
|
312
|
+
the cam property value
|
|
313
|
+
'''
|
|
314
|
+
return self._cam
|
|
315
|
+
|
|
316
|
+
@cam.setter
|
|
317
|
+
def cam(self, cam: miCamera):
|
|
318
|
+
'''The Camera property.
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
cam : object
|
|
323
|
+
the object to set as panel camera.
|
|
324
|
+
'''
|
|
325
|
+
self._cam = cam
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def buffer(self):
|
|
329
|
+
'''Gets the frames Queue
|
|
330
|
+
|
|
331
|
+
Returns
|
|
332
|
+
-------
|
|
333
|
+
Queue
|
|
334
|
+
Frames buffer.
|
|
335
|
+
'''
|
|
336
|
+
if self._frames is not None:
|
|
337
|
+
return self._frames
|
|
338
|
+
else:
|
|
339
|
+
return Queue()
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def bufferSize(self):
|
|
343
|
+
'''Gets frames Queue size.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
int
|
|
348
|
+
Frames Queue size.
|
|
349
|
+
'''
|
|
350
|
+
if self._frames is not None:
|
|
351
|
+
return self._frames.qsize()
|
|
352
|
+
else:
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def isEmpty(self) -> bool:
|
|
357
|
+
'''Is frames Queue empty.
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
bool
|
|
362
|
+
True if frames Queue is empty, otherwise False.
|
|
363
|
+
'''
|
|
364
|
+
return self._frames.empty()
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def Event(self):
|
|
368
|
+
'''The acquisition job threading Event.'''
|
|
369
|
+
if hasattr(self, '_acqEvent'):
|
|
370
|
+
return self._acqEvent
|
|
371
|
+
else:
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
@Event.setter
|
|
375
|
+
def Event(self, value: threading.Event):
|
|
376
|
+
'''The acquisition job threading Event.'''
|
|
377
|
+
if value is None:
|
|
378
|
+
self._acqEvent = threading.Event()
|
|
379
|
+
else:
|
|
380
|
+
self._acqEvent = value
|
|
381
|
+
|
|
382
|
+
def get_event_action(self, param: str):
|
|
383
|
+
return {'name': str(param), 'type': 'action', 'event': 'Event'}
|
|
384
|
+
|
|
385
|
+
def __str__(self):
|
|
386
|
+
return f'{self.cam.name}'
|
|
387
|
+
|
|
388
|
+
def get(self, last=False) -> np.ndarray:
|
|
389
|
+
'''Gets image from frames Queue in FIFO or LIFO manner.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
last : bool, optional
|
|
394
|
+
Gets image from frames Queue in LIFO manner if True, by default False.
|
|
395
|
+
|
|
396
|
+
Returns
|
|
397
|
+
-------
|
|
398
|
+
np.ndarray
|
|
399
|
+
The image first or last in buffer.
|
|
400
|
+
'''
|
|
401
|
+
res = None
|
|
402
|
+
if not self._frames.empty():
|
|
403
|
+
if last:
|
|
404
|
+
while not self.isEmpty:
|
|
405
|
+
res = self._frames.get()
|
|
406
|
+
else:
|
|
407
|
+
res = self._frames.get()
|
|
408
|
+
else:
|
|
409
|
+
res = np.random.normal(size=(256, 256))
|
|
410
|
+
return res
|
|
411
|
+
|
|
412
|
+
def setExposure(self, value: float):
|
|
413
|
+
'''Sets the exposure time widget of camera
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
value : float
|
|
418
|
+
selected exposure time
|
|
419
|
+
'''
|
|
420
|
+
self.camera_options.set_param_value(CamParams.EXPOSURE, value)
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def isOpen(self) -> bool:
|
|
424
|
+
'''Returns is acquisition active?
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
bool
|
|
429
|
+
True if acquiring, otherwise False.
|
|
430
|
+
'''
|
|
431
|
+
return self.cam.acquisition
|
|
432
|
+
|
|
433
|
+
def set_ROI(self):
|
|
434
|
+
'''Sets the ROI for the slected camera'''
|
|
435
|
+
pass
|
|
436
|
+
|
|
437
|
+
def reset_ROI(self):
|
|
438
|
+
'''Resets the ROI for the slected camera'''
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
def center_ROI(self):
|
|
442
|
+
'''Sets the ROI for the slected camera'''
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
def select_ROI(self):
|
|
446
|
+
'''Selects the ROI for the camera'''
|
|
447
|
+
raise NotImplementedError('The select_ROI function is not implemented yet.')
|
|
448
|
+
|
|
449
|
+
def select_ROIs(self):
|
|
450
|
+
'''Selects the ROI for the camera'''
|
|
451
|
+
raise NotImplementedError('The select_ROIs function is not implemented yet.')
|
|
452
|
+
|
|
453
|
+
def get_meta(self):
|
|
454
|
+
return self.camera_options.get_json()
|
|
455
|
+
|
|
456
|
+
def getAcquisitionJob(self, event=None) -> AcquisitionJob:
|
|
457
|
+
self._save_path = os.path.join(
|
|
458
|
+
self._directory,
|
|
459
|
+
self.camera_options.get_param_value(CamParams.EXPERIMENT_NAME) + '\\',
|
|
460
|
+
)
|
|
461
|
+
self.Event = event
|
|
462
|
+
return AcquisitionJob(
|
|
463
|
+
self._temps,
|
|
464
|
+
self._buffer,
|
|
465
|
+
self._frames,
|
|
466
|
+
self._save_path,
|
|
467
|
+
self._cam.getHeight(),
|
|
468
|
+
self._cam.getWidth(),
|
|
469
|
+
biggTiff=self.camera_options.isBiggTiff,
|
|
470
|
+
bytes_per_pixel=self._cam.bytes_per_pixel,
|
|
471
|
+
exposure=self._cam.getExposure(),
|
|
472
|
+
frames=self.camera_options.get_param_value(CamParams.FRAMES),
|
|
473
|
+
full_tif_meta=self.camera_options.isFullMetadata,
|
|
474
|
+
is_dark_cal=self.camera_options.isDarkCalibration,
|
|
475
|
+
meta_file=self.get_meta(),
|
|
476
|
+
meta_func=self.OME_tab.gen_OME_XML
|
|
477
|
+
if self.camera_options.isFullMetadata
|
|
478
|
+
else self.OME_tab.gen_OME_XML_short,
|
|
479
|
+
name=self._cam.name,
|
|
480
|
+
prefix=self._save_prefix,
|
|
481
|
+
save=self.camera_options.isSaveData,
|
|
482
|
+
Zarr=not self.camera_options.isTiff,
|
|
483
|
+
rois=self.camera_options.get_export_rois(),
|
|
484
|
+
seperate_rois=self.camera_options.get_param_value(
|
|
485
|
+
CamParams.EXPORT_ROIS_SEPERATE
|
|
486
|
+
),
|
|
487
|
+
flip_rois=self.camera_options.get_param_value(
|
|
488
|
+
CamParams.EXPORT_ROIS_FLIPPED
|
|
489
|
+
),
|
|
490
|
+
s_event=self.Event,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
def view_toggled(self):
|
|
494
|
+
if self.camera_options.isSingleView:
|
|
495
|
+
self.lr_0.setSpan(0, 1)
|
|
496
|
+
self.lr_1.setMovable(False)
|
|
497
|
+
self.lr_1.setRegion((0, self._nBins))
|
|
498
|
+
self.lr_1.setSpan(1.0, 1.0)
|
|
499
|
+
else:
|
|
500
|
+
self.lr_0.setSpan(0, 0.5)
|
|
501
|
+
self.lr_1.setMovable(True)
|
|
502
|
+
self.lr_1.setSpan(0.5, 1.0)
|
|
503
|
+
|
|
504
|
+
@Slot()
|
|
505
|
+
def directory_changed(self, value: str):
|
|
506
|
+
'''Slot for directory changed signal'''
|
|
507
|
+
if len(value) > 0:
|
|
508
|
+
self._directory = value
|
|
509
|
+
|
|
510
|
+
def start_free_run(self, param=None, Prefix='', Event=None):
|
|
511
|
+
'''
|
|
512
|
+
Starts free run acquisition mode
|
|
513
|
+
|
|
514
|
+
Parameters
|
|
515
|
+
----------
|
|
516
|
+
param : Parameter
|
|
517
|
+
the parameter that was activated.
|
|
518
|
+
Prefix : str
|
|
519
|
+
an extra prefix added to the image stack file name.
|
|
520
|
+
'''
|
|
521
|
+
if self._cam.acquisition:
|
|
522
|
+
return # if acquisition is already going on
|
|
523
|
+
|
|
524
|
+
self._save_prefix = Prefix
|
|
525
|
+
self.acq_job = self.getAcquisitionJob(Event)
|
|
526
|
+
|
|
527
|
+
self._cam.acquisition = True # set acquisition flag to true
|
|
528
|
+
|
|
529
|
+
# start both capture and display workers
|
|
530
|
+
self.start_all_workers()
|
|
531
|
+
|
|
532
|
+
def stop(self):
|
|
533
|
+
if self.acq_job is not None:
|
|
534
|
+
self.acq_job.c_event.set()
|
|
535
|
+
# set stop acquisition workers flag to true
|
|
536
|
+
if self.acq_job is not None:
|
|
537
|
+
self.acq_job.stop_threads = True
|
|
538
|
+
|
|
539
|
+
def start_all_workers(self):
|
|
540
|
+
'''
|
|
541
|
+
Starts all workers
|
|
542
|
+
'''
|
|
543
|
+
|
|
544
|
+
self._buffer.queue.clear()
|
|
545
|
+
self._temps.queue.clear()
|
|
546
|
+
self._frames.queue.clear()
|
|
547
|
+
self.time = QDateTime.currentDateTime()
|
|
548
|
+
|
|
549
|
+
# Pass the display function to be executed
|
|
550
|
+
if self.d_worker is None or self.d_worker.is_set():
|
|
551
|
+
self.d_worker = QThreadWorker(cam_display, self.acq_job, self)
|
|
552
|
+
|
|
553
|
+
self.d_worker.signals.finished.connect(
|
|
554
|
+
lambda: self.acq_job.setDone(1, True)
|
|
555
|
+
)
|
|
556
|
+
# Execute
|
|
557
|
+
self.d_worker.setAutoDelete(True)
|
|
558
|
+
self._threadpool.start(self.d_worker)
|
|
559
|
+
|
|
560
|
+
# Pass the save function to be executed
|
|
561
|
+
if not self.mini:
|
|
562
|
+
if self.s_worker is None or self.s_worker.is_set():
|
|
563
|
+
self.s_worker = QThreadWorker(cam_save, self.acq_job)
|
|
564
|
+
self.s_worker.setAutoDelete(True)
|
|
565
|
+
# Execute
|
|
566
|
+
self._threadpool.start(self.s_worker)
|
|
567
|
+
|
|
568
|
+
# Pass the capture function to be executed
|
|
569
|
+
if self.c_worker is None or self.c_worker.is_set():
|
|
570
|
+
# Any other args, kwargs are passed to the run function
|
|
571
|
+
self.c_worker = QThreadWorker(self.cam_capture, *self.getCaptureArgs())
|
|
572
|
+
self.c_worker.signals.finished.connect(
|
|
573
|
+
lambda: self.acq_job.setDone(0, True)
|
|
574
|
+
)
|
|
575
|
+
self.c_worker.setAutoDelete(True)
|
|
576
|
+
# Execute
|
|
577
|
+
self._threadpool.start(self.c_worker)
|
|
578
|
+
|
|
579
|
+
def updateInfo(self):
|
|
580
|
+
if isinstance(self.acq_job, AcquisitionJob):
|
|
581
|
+
self.camera_options.set_param_value(
|
|
582
|
+
CamParams.TEMPERATURE, f'T {self._cam.temperature:.2f} °C'
|
|
583
|
+
)
|
|
584
|
+
self.camera_options.set_param_value(
|
|
585
|
+
CamParams.CAPTURE_STATS,
|
|
586
|
+
(
|
|
587
|
+
f'Capture {self.acq_job.frames_captured}/{self.acq_job.frames} '
|
|
588
|
+
+ f'{self.acq_job.frames_captured / self.acq_job.frames:.2%} '
|
|
589
|
+
+ f'| {self.acq_job.capture_time:.2f} ms'
|
|
590
|
+
),
|
|
591
|
+
)
|
|
592
|
+
self.camera_options.set_param_value(
|
|
593
|
+
CamParams.DISPLAY_STATS,
|
|
594
|
+
f'Display {self._buffer.qsize()} | {self.acq_job.display_time:.2f} ms',
|
|
595
|
+
)
|
|
596
|
+
self.camera_options.set_param_value(
|
|
597
|
+
CamParams.SAVE_STATS,
|
|
598
|
+
f'Save {self._frames.qsize():d} | {self.acq_job.save_time:.2f} ms ',
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
def getCaptureArgs(self) -> list:
|
|
602
|
+
'''User specific arguments to be passed to the parallelized
|
|
603
|
+
Camera_Panel.cam_capture function.
|
|
604
|
+
|
|
605
|
+
Example
|
|
606
|
+
------
|
|
607
|
+
check child panels (vimba, ueye or thorlabs)
|
|
608
|
+
|
|
609
|
+
Returns
|
|
610
|
+
-------
|
|
611
|
+
list
|
|
612
|
+
list of args to be passed in order to
|
|
613
|
+
Camera_Panel.cam_capture function.
|
|
614
|
+
|
|
615
|
+
Raises
|
|
616
|
+
------
|
|
617
|
+
NotImplementedError
|
|
618
|
+
Has to be implemented by the use in child class.
|
|
619
|
+
'''
|
|
620
|
+
args = []
|
|
621
|
+
raise NotImplementedError('The getCaptureArgs function is not implemented yet.')
|
|
622
|
+
|
|
623
|
+
def cam_capture(self, *args, **kwargs):
|
|
624
|
+
'''User specific implemented logic for frame capture
|
|
625
|
+
from a miCamera adapter.
|
|
626
|
+
|
|
627
|
+
Example
|
|
628
|
+
------
|
|
629
|
+
check child panels (vimba, ueye or thorlabs)
|
|
630
|
+
|
|
631
|
+
Raises
|
|
632
|
+
------
|
|
633
|
+
NotImplementedError
|
|
634
|
+
Has to be implemented by the use in child class.
|
|
635
|
+
'''
|
|
636
|
+
raise NotImplementedError('The cam_capture function is not implemented yet.')
|
|
637
|
+
|
|
638
|
+
def updateStats(self, frames: tuple[uImage]):
|
|
639
|
+
now = time.time()
|
|
640
|
+
if now - self.__update_timer < 0.1:
|
|
641
|
+
return
|
|
642
|
+
|
|
643
|
+
self.__update_timer = now
|
|
644
|
+
for idx, frame in enumerate(frames):
|
|
645
|
+
if idx < 2:
|
|
646
|
+
_lr = self.lr_0 if idx == 0 else self.lr_1
|
|
647
|
+
_plot_ref = self._plot_ref if idx == 0 else self._plot_ref_2
|
|
648
|
+
_cdf_plot_ref = self._cdf_plot_ref if idx == 0 else self._cdf_plot_ref_2
|
|
649
|
+
|
|
650
|
+
if frame is not None:
|
|
651
|
+
if self.camera_options.isAutostretch:
|
|
652
|
+
_lr.setRegion((frame._min, frame._max))
|
|
653
|
+
|
|
654
|
+
_plot_ref.setData(frame._hist)
|
|
655
|
+
_cdf_plot_ref.setData(frame._cdf)
|
|
656
|
+
else:
|
|
657
|
+
_plot_ref.setData(self._hist)
|
|
658
|
+
_cdf_plot_ref.setData(self._hist)
|
|
659
|
+
else:
|
|
660
|
+
break
|
|
661
|
+
|
|
662
|
+
def updateRange(self, rmin: int, rmax: int):
|
|
663
|
+
if time.time() - self.__update_timer < 0.1:
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
if self.camera_options.isAutostretch:
|
|
667
|
+
self.histogram.setXRange(rmin, rmax)
|
|
668
|
+
self.hist_cdf.setXRange(rmin, rmax)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def cam_save(params: AcquisitionJob, **kwargs):
|
|
672
|
+
'''Save function executed by the save worker thread.
|
|
673
|
+
|
|
674
|
+
Saves the acquired frames using the config in AcquisitionJob
|
|
675
|
+
with a csv file containing the sensor temp for each frame.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
params : AcquisitionJob
|
|
680
|
+
class holding cross-thread info required for an Acquisition Job.
|
|
681
|
+
'''
|
|
682
|
+
try:
|
|
683
|
+
while True:
|
|
684
|
+
# save in case frame stack is not empty
|
|
685
|
+
if not params.save_queue.empty():
|
|
686
|
+
# for save time estimations
|
|
687
|
+
save_time = QDateTime.currentDateTime()
|
|
688
|
+
|
|
689
|
+
# get frame and temp to save from bottom of stack
|
|
690
|
+
frame, temp = params.save_queue.get()
|
|
691
|
+
|
|
692
|
+
if params.frames_saved < params.frames:
|
|
693
|
+
# opens csv file and appends sensor temp and close
|
|
694
|
+
params.addTemp(temp)
|
|
695
|
+
# adds dark frame for camera calibration if needed
|
|
696
|
+
params.addDarkFrame(frame)
|
|
697
|
+
# saves frame to storage
|
|
698
|
+
params.addFrame(frame)
|
|
699
|
+
|
|
700
|
+
# for save time estimations
|
|
701
|
+
with params.lock:
|
|
702
|
+
params.save_time = save_time.msecsTo(QDateTime.currentDateTime())
|
|
703
|
+
|
|
704
|
+
params.frames_saved += 1
|
|
705
|
+
|
|
706
|
+
QtCore.QThread.usleep(100)
|
|
707
|
+
# Flag that ends the loop
|
|
708
|
+
if (
|
|
709
|
+
params.save_queue.empty()
|
|
710
|
+
and params.stop_threads
|
|
711
|
+
and params.display_done
|
|
712
|
+
):
|
|
713
|
+
print('Save thread break.')
|
|
714
|
+
break
|
|
715
|
+
except Exception:
|
|
716
|
+
traceback.print_exc()
|
|
717
|
+
finally:
|
|
718
|
+
params.s_event.set()
|
|
719
|
+
params.finalize()
|
|
720
|
+
print('Save thread finally finished.')
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
@lru_cache(maxsize=128)
|
|
724
|
+
def get_font_settings():
|
|
725
|
+
return cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2, 255
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def drawStats(frame: uImage):
|
|
729
|
+
lines = [
|
|
730
|
+
f'min/max: {np.min(frame.image)}/{np.max(frame.image)}',
|
|
731
|
+
f'mean: {np.mean(frame.image):.3f}',
|
|
732
|
+
f'median: {np.median(frame.image):.3f}',
|
|
733
|
+
f'std: {np.std(frame.image):.4f}',
|
|
734
|
+
]
|
|
735
|
+
|
|
736
|
+
# Font settings
|
|
737
|
+
font, font_scale, font_thickness, font_color = get_font_settings()
|
|
738
|
+
|
|
739
|
+
# Position to start adding text
|
|
740
|
+
x, y = 50, 50
|
|
741
|
+
|
|
742
|
+
# Add each line of text to the image
|
|
743
|
+
for line in lines:
|
|
744
|
+
cv2.putText(
|
|
745
|
+
frame._view, line, (x, y), font, font_scale, font_color, font_thickness
|
|
746
|
+
)
|
|
747
|
+
y += int(2 * font_scale * 20) # Adjust vertical spacing
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def update_histogram_and_display(params: AcquisitionJob, camp: Camera_Panel):
|
|
751
|
+
'''
|
|
752
|
+
Update histogram and display based on camera options.
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
params : AcquisitionJob
|
|
757
|
+
Class holding cross-thread info required for an Acquisition Job.
|
|
758
|
+
camp : Camera_Panel
|
|
759
|
+
The camera panel to display data.
|
|
760
|
+
'''
|
|
761
|
+
# Common logic for updating histogram and display
|
|
762
|
+
if camp.camera_options.isSingleView or (
|
|
763
|
+
camp.camera_options.isROIsView and len(params.rois) == 0
|
|
764
|
+
):
|
|
765
|
+
_range = (
|
|
766
|
+
None
|
|
767
|
+
if camp.camera_options.isAutostretch
|
|
768
|
+
else tuple(map(math.ceil, camp.lr_0.getRegion()))
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
params.frame.equalizeLUT(_range, camp.camera_options.isNumpyLUT)
|
|
772
|
+
|
|
773
|
+
camp.updateRangeSignal.emit(params.frame._min, params.frame._max)
|
|
774
|
+
|
|
775
|
+
camp.updateStatsSignal.emit((params.frame, None))
|
|
776
|
+
|
|
777
|
+
# resizing the image
|
|
778
|
+
zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
|
|
779
|
+
|
|
780
|
+
if camp.camera_options.isDisplayStats:
|
|
781
|
+
drawStats(params.frame)
|
|
782
|
+
|
|
783
|
+
params.frame._view = cv2.resize(
|
|
784
|
+
params.frame._view,
|
|
785
|
+
(0, 0),
|
|
786
|
+
fx=zoom,
|
|
787
|
+
fy=zoom,
|
|
788
|
+
interpolation=cv2.INTER_NEAREST,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# display it
|
|
792
|
+
cv2.imshow(params.name, params.frame._view)
|
|
793
|
+
elif camp.camera_options.isROIsView:
|
|
794
|
+
if len(params.rois) == 1:
|
|
795
|
+
camp._plot_ref_2.setData(camp._hist)
|
|
796
|
+
camp._cdf_plot_ref_2.setData(camp._hist)
|
|
797
|
+
images, mins, maxs = [], [], []
|
|
798
|
+
for idx, roi in enumerate(params.rois):
|
|
799
|
+
uimage = uImage(
|
|
800
|
+
params.frame.image[roi[1] : roi[1] + roi[3], roi[0] : roi[0] + roi[2]]
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
if idx > 0 and params.flip_rois:
|
|
804
|
+
uimage.image = np.fliplr(uimage.image)
|
|
805
|
+
|
|
806
|
+
_range = None
|
|
807
|
+
# image stretching
|
|
808
|
+
if not camp.camera_options.isAutostretch and idx < 2:
|
|
809
|
+
linear_region = camp.lr_0 if idx == 0 else camp.lr_1
|
|
810
|
+
_range = tuple(map(math.ceil, linear_region.getRegion()))
|
|
811
|
+
|
|
812
|
+
uimage.equalizeLUT(_range, camp.camera_options.isNumpyLUT)
|
|
813
|
+
|
|
814
|
+
mins.append(uimage._min)
|
|
815
|
+
maxs.append(uimage._max)
|
|
816
|
+
images.append(uimage)
|
|
817
|
+
|
|
818
|
+
# resizing the image
|
|
819
|
+
zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
|
|
820
|
+
uimage._view = cv2.resize(
|
|
821
|
+
uimage._view, (0, 0), fx=zoom, fy=zoom, interpolation=cv2.INTER_NEAREST
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
# display it
|
|
825
|
+
cv2.imshow(params.name + f' ROI {idx + 1}', uimage._view)
|
|
826
|
+
|
|
827
|
+
camp.updateRangeSignal.emit(min(mins), max(maxs))
|
|
828
|
+
camp.updateStatsSignal.emit(tuple(images))
|
|
829
|
+
else:
|
|
830
|
+
_rang_left = None
|
|
831
|
+
_rang_right = None
|
|
832
|
+
|
|
833
|
+
# image stretching
|
|
834
|
+
if not camp.camera_options.isAutostretch:
|
|
835
|
+
_rang_left = tuple(map(math.ceil, camp.lr_0.getRegion()))
|
|
836
|
+
_rang_right = tuple(map(math.ceil, camp.lr_1.getRegion()))
|
|
837
|
+
|
|
838
|
+
left, right = params.frame.hsplitView()
|
|
839
|
+
|
|
840
|
+
left.equalizeLUT(_rang_left, camp.camera_options.isNumpyLUT)
|
|
841
|
+
right.equalizeLUT(_rang_right, camp.camera_options.isNumpyLUT)
|
|
842
|
+
|
|
843
|
+
camp.updateRangeSignal.emit(
|
|
844
|
+
min(left._min, right._min), max(left._max, right._max)
|
|
845
|
+
)
|
|
846
|
+
camp.updateStatsSignal.emit((left, right))
|
|
847
|
+
|
|
848
|
+
zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
|
|
849
|
+
if camp.camera_options.isOverlaidView:
|
|
850
|
+
_img = np.zeros(left._view.shape[:2] + (3,), dtype=np.uint8)
|
|
851
|
+
_img[..., 1] = left._view
|
|
852
|
+
_img[..., 0] = right._view
|
|
853
|
+
_img = cv2.resize(
|
|
854
|
+
_img, (0, 0), fx=zoom, fy=zoom, interpolation=cv2.INTER_NEAREST
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
cv2.imshow(params.name, _img)
|
|
858
|
+
else:
|
|
859
|
+
_img = cv2.resize(
|
|
860
|
+
np.concatenate([left._view, right._view], axis=1),
|
|
861
|
+
(0, 0),
|
|
862
|
+
fx=zoom,
|
|
863
|
+
fy=zoom,
|
|
864
|
+
interpolation=cv2.INTER_NEAREST,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
cv2.imshow(params.name, _img)
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def cam_display(params: AcquisitionJob, camp: Camera_Panel, **kwargs):
|
|
871
|
+
'''Display function executed by the display worker.
|
|
872
|
+
|
|
873
|
+
Processes the acquired frame, displays it, and sends it to the save
|
|
874
|
+
stack.
|
|
875
|
+
|
|
876
|
+
Parameters
|
|
877
|
+
----------
|
|
878
|
+
params : AcquisitionJob
|
|
879
|
+
class holding cross-thread info required for an Acquisition Job.
|
|
880
|
+
camp : Camera_Panel
|
|
881
|
+
the camera panel to display data.
|
|
882
|
+
'''
|
|
883
|
+
try:
|
|
884
|
+
time = QDateTime.currentDateTime()
|
|
885
|
+
|
|
886
|
+
# Continuous image display
|
|
887
|
+
while True:
|
|
888
|
+
# proceed only if the buffer is not empty
|
|
889
|
+
if not params.display_queue.empty():
|
|
890
|
+
# for display time estimations
|
|
891
|
+
with params.lock:
|
|
892
|
+
params.display_time = time.msecsTo(QDateTime.currentDateTime())
|
|
893
|
+
time = QDateTime.currentDateTime()
|
|
894
|
+
|
|
895
|
+
# reshape image into proper shape
|
|
896
|
+
# (height, width, bytes per pixel)
|
|
897
|
+
params.frame = uImage.fromBuffer(
|
|
898
|
+
params.display_queue.get(),
|
|
899
|
+
params.cam_height,
|
|
900
|
+
params.cam_width,
|
|
901
|
+
params.bytes_per_pixel,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# add to saving stack
|
|
905
|
+
if params.save:
|
|
906
|
+
if not camp.mini:
|
|
907
|
+
params.save_queue.put(
|
|
908
|
+
(params.frame.image, params.temp_queue.get())
|
|
909
|
+
)
|
|
910
|
+
else:
|
|
911
|
+
params.save_queue.put(params.frame.image)
|
|
912
|
+
|
|
913
|
+
if camp.camera_options.isPreview:
|
|
914
|
+
if camp.camera_options.isLineProfiler:
|
|
915
|
+
camp.lineProfiler.imageUpdate.emit(params.frame._image)
|
|
916
|
+
|
|
917
|
+
update_histogram_and_display(params, camp)
|
|
918
|
+
cv2.waitKey(1)
|
|
919
|
+
else:
|
|
920
|
+
cv2.waitKey(1)
|
|
921
|
+
else:
|
|
922
|
+
cv2.waitKey(1)
|
|
923
|
+
|
|
924
|
+
QtCore.QThread.usleep(100)
|
|
925
|
+
# Flag that ends the loop
|
|
926
|
+
if (
|
|
927
|
+
params.stop_threads
|
|
928
|
+
and params.display_queue.empty()
|
|
929
|
+
and params.capture_done
|
|
930
|
+
):
|
|
931
|
+
print('Display thread break.')
|
|
932
|
+
break
|
|
933
|
+
except Exception:
|
|
934
|
+
traceback.print_exc()
|
|
935
|
+
finally:
|
|
936
|
+
try:
|
|
937
|
+
if not camp.mini:
|
|
938
|
+
cv2.destroyAllWindows()
|
|
939
|
+
except Exception:
|
|
940
|
+
traceback.print_exc()
|
|
941
|
+
print('Display thread finally finished.')
|