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,415 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pyqtgraph as pg
|
|
3
|
+
|
|
4
|
+
from microEye.qt import QApplication, Qt, QtGui, QtWidgets, Signal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ImageItemOptions(QtWidgets.QWidget):
|
|
8
|
+
paramsChanged = Signal(int, str, object) # layer_index, param_name, value
|
|
9
|
+
|
|
10
|
+
def __init__(self, layer_index, parent=None):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self.layer_index = layer_index
|
|
13
|
+
|
|
14
|
+
layout = QtWidgets.QVBoxLayout()
|
|
15
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
16
|
+
self.setLayout(layout)
|
|
17
|
+
|
|
18
|
+
# Visible checkbox
|
|
19
|
+
self.visible_check = QtWidgets.QCheckBox('Visible')
|
|
20
|
+
self.visible_check.setChecked(True)
|
|
21
|
+
self.visible_check.stateChanged.connect(
|
|
22
|
+
lambda state: self.paramsChanged.emit(
|
|
23
|
+
self.layer_index, 'Visible', bool(state)
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
layout.addWidget(self.visible_check)
|
|
27
|
+
|
|
28
|
+
# Opacity slider with value
|
|
29
|
+
opacity_widget = QtWidgets.QWidget()
|
|
30
|
+
opacity_layout = QtWidgets.QVBoxLayout()
|
|
31
|
+
opacity_layout.setContentsMargins(0, 0, 0, 0)
|
|
32
|
+
opacity_widget.setLayout(opacity_layout)
|
|
33
|
+
|
|
34
|
+
opacity_layout.addWidget(QtWidgets.QLabel('Opacity:'))
|
|
35
|
+
|
|
36
|
+
opacity_slider_layout = QtWidgets.QHBoxLayout()
|
|
37
|
+
self.opacity_slider = QtWidgets.QSlider(Qt.Orientation.Horizontal)
|
|
38
|
+
self.opacity_slider.setRange(0, 100)
|
|
39
|
+
self.opacity_slider.setValue(100)
|
|
40
|
+
self.opacity_slider.valueChanged.connect(
|
|
41
|
+
lambda value: self.paramsChanged.emit(self.layer_index, 'Opacity', value)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.opacity_value = QtWidgets.QSpinBox()
|
|
45
|
+
self.opacity_value.setRange(0, 100)
|
|
46
|
+
self.opacity_value.setValue(100)
|
|
47
|
+
self.opacity_value.setFixedWidth(75)
|
|
48
|
+
|
|
49
|
+
self.opacity_value.valueChanged.connect(self.opacity_slider.setValue)
|
|
50
|
+
self.opacity_slider.valueChanged.connect(self.opacity_value.setValue)
|
|
51
|
+
|
|
52
|
+
opacity_slider_layout.addWidget(self.opacity_slider)
|
|
53
|
+
opacity_slider_layout.addWidget(self.opacity_value)
|
|
54
|
+
opacity_layout.addLayout(opacity_slider_layout)
|
|
55
|
+
|
|
56
|
+
layout.addWidget(opacity_widget)
|
|
57
|
+
|
|
58
|
+
# Composition mode
|
|
59
|
+
comp_widget = QtWidgets.QWidget()
|
|
60
|
+
comp_layout = QtWidgets.QVBoxLayout()
|
|
61
|
+
comp_layout.setContentsMargins(0, 0, 0, 0)
|
|
62
|
+
comp_widget.setLayout(comp_layout)
|
|
63
|
+
|
|
64
|
+
comp_layout.addWidget(QtWidgets.QLabel('Composition Mode:'))
|
|
65
|
+
self.comp_combo = QtWidgets.QComboBox()
|
|
66
|
+
self.comp_combo.addItems(ImageItemsWidget.COMP_MODES)
|
|
67
|
+
self.comp_combo.setCurrentText('SourceOver')
|
|
68
|
+
self.comp_combo.currentTextChanged.connect(
|
|
69
|
+
lambda text: self.paramsChanged.emit(
|
|
70
|
+
self.layer_index, 'CompositionMode', text
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
comp_layout.addWidget(self.comp_combo)
|
|
74
|
+
|
|
75
|
+
layout.addWidget(comp_widget)
|
|
76
|
+
layout.addStretch()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CustomListWidget(QtWidgets.QListWidget):
|
|
80
|
+
'''Custom list widget with enhanced font handling
|
|
81
|
+
and curent item index next to it.'''
|
|
82
|
+
|
|
83
|
+
def __init__(self, parent=None):
|
|
84
|
+
super().__init__(parent)
|
|
85
|
+
self.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame)
|
|
86
|
+
self.setSpacing(2)
|
|
87
|
+
|
|
88
|
+
# Set larger font size for all items
|
|
89
|
+
font = self.font()
|
|
90
|
+
font.setPointSize(10) # Adjust size as needed
|
|
91
|
+
self.setFont(font)
|
|
92
|
+
|
|
93
|
+
def addItem(self, item):
|
|
94
|
+
'''Override addItem to add index prefix to items'''
|
|
95
|
+
if isinstance(item, str):
|
|
96
|
+
item = QtWidgets.QListWidgetItem(item)
|
|
97
|
+
|
|
98
|
+
# Get the new item's index
|
|
99
|
+
index = self.count() + 1 # 1-based index
|
|
100
|
+
# Update the text to include the index
|
|
101
|
+
current_text = item.text()
|
|
102
|
+
item.setText(f'[{index}] {current_text}')
|
|
103
|
+
|
|
104
|
+
super().addItem(item)
|
|
105
|
+
|
|
106
|
+
def currentChanged(self, current, previous):
|
|
107
|
+
# Update font weight for both items
|
|
108
|
+
if previous:
|
|
109
|
+
item = self.item(previous.row())
|
|
110
|
+
if item:
|
|
111
|
+
font = item.font()
|
|
112
|
+
font.setBold(False)
|
|
113
|
+
item.setFont(font)
|
|
114
|
+
|
|
115
|
+
if current:
|
|
116
|
+
item = self.item(current.row())
|
|
117
|
+
if item:
|
|
118
|
+
font = item.font()
|
|
119
|
+
font.setBold(True)
|
|
120
|
+
item.setFont(font)
|
|
121
|
+
|
|
122
|
+
super().currentChanged(current, previous)
|
|
123
|
+
|
|
124
|
+
def takeItem(self, row):
|
|
125
|
+
super().takeItem(row)
|
|
126
|
+
|
|
127
|
+
# Update indices after removal
|
|
128
|
+
self.updateIndices()
|
|
129
|
+
|
|
130
|
+
def updateIndices(self):
|
|
131
|
+
'''Update all item indices after removals or reordering'''
|
|
132
|
+
for i in range(self.count()):
|
|
133
|
+
item = self.item(i)
|
|
134
|
+
if item:
|
|
135
|
+
# Remove old index if present and add new one
|
|
136
|
+
text = item.text()
|
|
137
|
+
if ']' in text:
|
|
138
|
+
text = text.split(']', 1)[1].strip()
|
|
139
|
+
item.setText(f'[{i + 1}] {text}')
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ImageItemsWidget(QtWidgets.QWidget):
|
|
143
|
+
layerRemoved = Signal(int) # Signal emitted when user wants to remove a layer
|
|
144
|
+
layerChanged = Signal(
|
|
145
|
+
int, int, object
|
|
146
|
+
) # Signal emitted when user selects a different layer
|
|
147
|
+
|
|
148
|
+
COMP_MODES = [
|
|
149
|
+
'Clear',
|
|
150
|
+
'ColorBurn',
|
|
151
|
+
'ColorDodge',
|
|
152
|
+
'Darken',
|
|
153
|
+
'Destination',
|
|
154
|
+
'DestinationAtop',
|
|
155
|
+
'DestinationIn',
|
|
156
|
+
'DestinationOut',
|
|
157
|
+
'DestinationOver',
|
|
158
|
+
'Difference',
|
|
159
|
+
'Exclusion',
|
|
160
|
+
'HardLight',
|
|
161
|
+
'Lighten',
|
|
162
|
+
'Multiply',
|
|
163
|
+
'Overlay',
|
|
164
|
+
'Plus',
|
|
165
|
+
'Screen',
|
|
166
|
+
'SoftLight',
|
|
167
|
+
'Source',
|
|
168
|
+
'SourceAtop',
|
|
169
|
+
'SourceIn',
|
|
170
|
+
'SourceOut',
|
|
171
|
+
'SourceOver',
|
|
172
|
+
'Xor',
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
def __init__(self, debug=False):
|
|
176
|
+
super().__init__()
|
|
177
|
+
self._debug = debug
|
|
178
|
+
self._readonly = False
|
|
179
|
+
|
|
180
|
+
# Create main layout
|
|
181
|
+
main_layout = QtWidgets.QVBoxLayout()
|
|
182
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
183
|
+
self.setLayout(main_layout)
|
|
184
|
+
|
|
185
|
+
# Create splitter
|
|
186
|
+
self.splitter = QtWidgets.QSplitter(Qt.Orientation.Vertical)
|
|
187
|
+
self.splitter.setHandleWidth(1) # Minimal width for splitter handle
|
|
188
|
+
self.splitter.setStyleSheet('''
|
|
189
|
+
QSplitter::handle {
|
|
190
|
+
background-color: #999999;
|
|
191
|
+
}
|
|
192
|
+
''')
|
|
193
|
+
|
|
194
|
+
# Layer list section
|
|
195
|
+
list_widget = QtWidgets.QWidget()
|
|
196
|
+
list_layout = QtWidgets.QVBoxLayout()
|
|
197
|
+
list_layout.setContentsMargins(0, 0, 0, 0)
|
|
198
|
+
list_widget.setLayout(list_layout)
|
|
199
|
+
|
|
200
|
+
# Custom list widget with enhanced font handling
|
|
201
|
+
self.layer_list = CustomListWidget()
|
|
202
|
+
palette = self.layer_list.palette()
|
|
203
|
+
palette.setColor(QtGui.QPalette.ColorRole.Base, QtGui.QColor(240, 240, 240))
|
|
204
|
+
self.layer_list.setPalette(palette)
|
|
205
|
+
self.layer_list.currentItemChanged.connect(self._on_layer_selection_changed)
|
|
206
|
+
list_layout.addWidget(self.layer_list)
|
|
207
|
+
|
|
208
|
+
# Parameters section
|
|
209
|
+
self.params_container = QtWidgets.QWidget()
|
|
210
|
+
self.params_layout = QtWidgets.QVBoxLayout()
|
|
211
|
+
self.params_layout.setContentsMargins(0, 0, 0, 0)
|
|
212
|
+
self.params_container.setLayout(self.params_layout)
|
|
213
|
+
|
|
214
|
+
self.placeholder_label = QtWidgets.QLabel('Select a layer to view parameters')
|
|
215
|
+
self.placeholder_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
216
|
+
self.params_layout.addWidget(self.placeholder_label)
|
|
217
|
+
|
|
218
|
+
# Add widgets to splitter
|
|
219
|
+
self.splitter.addWidget(list_widget)
|
|
220
|
+
self.splitter.addWidget(self.params_container)
|
|
221
|
+
|
|
222
|
+
# Add splitter to main layout
|
|
223
|
+
main_layout.addWidget(self.splitter)
|
|
224
|
+
|
|
225
|
+
# remove layer button
|
|
226
|
+
self.remove_button = QtWidgets.QPushButton('Remove Layer')
|
|
227
|
+
self.remove_button.clicked.connect(self.remove_layer)
|
|
228
|
+
main_layout.addWidget(self.remove_button)
|
|
229
|
+
|
|
230
|
+
# Store layer parameter widgets
|
|
231
|
+
self.layer_params: list[ImageItemOptions] = []
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def currentIndex(self):
|
|
235
|
+
'''
|
|
236
|
+
Returns the index of the currently selected layer.
|
|
237
|
+
Returns -1 if no layer is selected.
|
|
238
|
+
'''
|
|
239
|
+
return self.layer_list.currentRow()
|
|
240
|
+
|
|
241
|
+
@currentIndex.setter
|
|
242
|
+
def currentIndex(self, index):
|
|
243
|
+
if not isinstance(index, int):
|
|
244
|
+
raise TypeError('Index must be an integer')
|
|
245
|
+
|
|
246
|
+
if index < 0 or index >= self.layer_list.count():
|
|
247
|
+
raise ValueError('Index out of range')
|
|
248
|
+
|
|
249
|
+
self.layer_list.setCurrentRow(index)
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def currentLayer(self) -> pg.ImageItem:
|
|
253
|
+
'''
|
|
254
|
+
Returns the currently selected layer.
|
|
255
|
+
Returns None if no layer is selected.
|
|
256
|
+
'''
|
|
257
|
+
index = self.currentIndex
|
|
258
|
+
if index < 0:
|
|
259
|
+
return None
|
|
260
|
+
return self.layer_list.item(index).data(Qt.ItemDataRole.UserRole)
|
|
261
|
+
|
|
262
|
+
def setReadonly(self, readonly: bool = True):
|
|
263
|
+
'''
|
|
264
|
+
Sets the widget to read-only mode.
|
|
265
|
+
In read-only mode, the user cannot remove layers.
|
|
266
|
+
'''
|
|
267
|
+
self._readonly = readonly
|
|
268
|
+
|
|
269
|
+
self.remove_button.setEnabled(not readonly)
|
|
270
|
+
|
|
271
|
+
def getImageItemAt(self, index: int) -> pg.ImageItem:
|
|
272
|
+
'''
|
|
273
|
+
Returns the image item at the given index.
|
|
274
|
+
Returns None if the index is out of range.
|
|
275
|
+
'''
|
|
276
|
+
# if negative index, count from the end
|
|
277
|
+
if index < 0:
|
|
278
|
+
index = self.layer_list.count() + index
|
|
279
|
+
|
|
280
|
+
if index < 0 or index >= self.layer_list.count():
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
return self.layer_list.item(index).data(Qt.ItemDataRole.UserRole)
|
|
284
|
+
|
|
285
|
+
def add_layer(
|
|
286
|
+
self,
|
|
287
|
+
image: np.ndarray,
|
|
288
|
+
opacity=1.0,
|
|
289
|
+
composition_mode='SourceOver',
|
|
290
|
+
name='Layer',
|
|
291
|
+
):
|
|
292
|
+
# Create the ImageItem and set its view to self.view_box
|
|
293
|
+
image_item = pg.ImageItem(
|
|
294
|
+
image, axisOrder='row-major', opacity=max(min(opacity, 1), 0)
|
|
295
|
+
)
|
|
296
|
+
image_item.setCompositionMode(
|
|
297
|
+
getattr(
|
|
298
|
+
QtGui.QPainter.CompositionMode, f'CompositionMode_{composition_mode}'
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
layer_index = self.count()
|
|
303
|
+
|
|
304
|
+
# Add to list widget
|
|
305
|
+
item = QtWidgets.QListWidgetItem(name)
|
|
306
|
+
item.setData(Qt.ItemDataRole.UserRole, image_item) # Store imageItem
|
|
307
|
+
self.layer_list.addItem(item)
|
|
308
|
+
|
|
309
|
+
# Create parameter widget
|
|
310
|
+
params_widget = ImageItemOptions(layer_index)
|
|
311
|
+
params_widget.paramsChanged.connect(self._on_params_changed)
|
|
312
|
+
params_widget.comp_combo.setCurrentText(composition_mode)
|
|
313
|
+
self.layer_params.append(params_widget)
|
|
314
|
+
|
|
315
|
+
# Select the new layer
|
|
316
|
+
self.layer_list.setCurrentItem(item)
|
|
317
|
+
|
|
318
|
+
return image_item
|
|
319
|
+
|
|
320
|
+
def remove_layer(self):
|
|
321
|
+
if self.layer_list.count() > 1:
|
|
322
|
+
current_row = self.layer_list.currentRow()
|
|
323
|
+
if current_row >= 0:
|
|
324
|
+
# Get the image item and its parent viewbox
|
|
325
|
+
image_item = self.currentLayer
|
|
326
|
+
if image_item:
|
|
327
|
+
# Remove from viewbox if it has a parent
|
|
328
|
+
if image_item.parentWidget():
|
|
329
|
+
image_item.parentWidget().removeItem(image_item)
|
|
330
|
+
# Clear the image data
|
|
331
|
+
image_item.clear()
|
|
332
|
+
# Set to None to help garbage collection
|
|
333
|
+
image_item.setParentItem(None)
|
|
334
|
+
|
|
335
|
+
self.layer_list.currentItem().setData(
|
|
336
|
+
Qt.ItemDataRole.UserRole, None
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Remove from list
|
|
340
|
+
self.layer_list.takeItem(current_row)
|
|
341
|
+
|
|
342
|
+
# Clean up and reorganize remaining layers
|
|
343
|
+
if current_row < len(self.layer_params):
|
|
344
|
+
self.layer_params[current_row].paramsChanged.disconnect()
|
|
345
|
+
self.layer_params[current_row].deleteLater()
|
|
346
|
+
del self.layer_params[current_row]
|
|
347
|
+
|
|
348
|
+
self.layerRemoved.emit(current_row)
|
|
349
|
+
self.layer_list.setCurrentRow(-1)
|
|
350
|
+
self.layer_list.setCurrentRow(current_row - 1)
|
|
351
|
+
|
|
352
|
+
def clear_layers(self):
|
|
353
|
+
self.layer_list.clear()
|
|
354
|
+
for widget in self.layer_params:
|
|
355
|
+
widget.paramsChanged.disconnect()
|
|
356
|
+
widget.deleteLater()
|
|
357
|
+
self.layer_params.clear()
|
|
358
|
+
|
|
359
|
+
def count(self):
|
|
360
|
+
return self.layer_list.count()
|
|
361
|
+
|
|
362
|
+
def _on_layer_selection_changed(self, current, previous):
|
|
363
|
+
# Clear current params layout
|
|
364
|
+
while self.params_layout.count():
|
|
365
|
+
item = self.params_layout.takeAt(0)
|
|
366
|
+
if item.widget():
|
|
367
|
+
item.widget().hide()
|
|
368
|
+
|
|
369
|
+
if current:
|
|
370
|
+
current_index = self.layer_list.row(current)
|
|
371
|
+
previous_index = self.layer_list.row(previous)
|
|
372
|
+
|
|
373
|
+
imageItem: pg.ImageItem = self.layer_list.currentItem().data(
|
|
374
|
+
Qt.ItemDataRole.UserRole
|
|
375
|
+
)
|
|
376
|
+
if current_index < len(self.layer_params):
|
|
377
|
+
params_widget = self.layer_params[current_index]
|
|
378
|
+
self.params_layout.addWidget(params_widget)
|
|
379
|
+
params_widget.show()
|
|
380
|
+
self.placeholder_label.hide()
|
|
381
|
+
self.layerChanged.emit(current_index, previous_index, imageItem)
|
|
382
|
+
else:
|
|
383
|
+
self.params_layout.addWidget(self.placeholder_label)
|
|
384
|
+
self.placeholder_label.show()
|
|
385
|
+
self.layerChanged.emit(-1, -1, None)
|
|
386
|
+
|
|
387
|
+
def _on_params_changed(self, layer_index, parameter, value):
|
|
388
|
+
if self._debug:
|
|
389
|
+
print(f'Parameter changed:')
|
|
390
|
+
print(f' Layer index: {layer_index}')
|
|
391
|
+
print(f' Parameter: {parameter}')
|
|
392
|
+
print(f' Value: {value}')
|
|
393
|
+
print(' ----------')
|
|
394
|
+
|
|
395
|
+
if self.layer_list.currentItem() is None:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
imageItem: pg.ImageItem = self.layer_list.currentItem().data(
|
|
399
|
+
Qt.ItemDataRole.UserRole
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if parameter in ['Opacity', 'Visible']:
|
|
403
|
+
value = value / 100 if parameter == 'Opacity' else value
|
|
404
|
+
imageItem.setOpts(opacity=float(value))
|
|
405
|
+
elif parameter == 'CompositionMode':
|
|
406
|
+
imageItem.setCompositionMode(
|
|
407
|
+
getattr(QtGui.QPainter.CompositionMode, f'CompositionMode_{value}')
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
if __name__ == '__main__':
|
|
412
|
+
app = QApplication([])
|
|
413
|
+
my_app = ImageItemsWidget()
|
|
414
|
+
my_app.show()
|
|
415
|
+
app.exec()
|