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.
Files changed (190) hide show
  1. microEye/__init__.py +47 -0
  2. microEye/_version.py +2 -0
  3. microEye/analysis/__init__.py +1 -0
  4. microEye/analysis/checklist_dialog.py +143 -0
  5. microEye/analysis/cmosMaps.py +228 -0
  6. microEye/analysis/filters/__init__.py +9 -0
  7. microEye/analysis/filters/base.py +21 -0
  8. microEye/analysis/filters/spatial.py +338 -0
  9. microEye/analysis/filters/temporal.py +76 -0
  10. microEye/analysis/fitting/__init__.py +0 -0
  11. microEye/analysis/fitting/fit.py +680 -0
  12. microEye/analysis/fitting/nena.py +375 -0
  13. microEye/analysis/fitting/phasor_fit.py +90 -0
  14. microEye/analysis/fitting/processing.py +317 -0
  15. microEye/analysis/fitting/psf/__init__.py +6 -0
  16. microEye/analysis/fitting/psf/extract.py +1129 -0
  17. microEye/analysis/fitting/psf/rubost_mean.py +150 -0
  18. microEye/analysis/fitting/psf/spline.py +167 -0
  19. microEye/analysis/fitting/psf/stats/__init__.py +12 -0
  20. microEye/analysis/fitting/psf/stats/core.py +295 -0
  21. microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
  22. microEye/analysis/fitting/psf/stats/io.py +104 -0
  23. microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
  24. microEye/analysis/fitting/psf/temp.py +147 -0
  25. microEye/analysis/fitting/psf/test.py +47 -0
  26. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
  27. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
  28. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
  29. microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
  30. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
  31. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
  32. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
  33. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
  34. microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
  35. microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
  36. microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
  37. microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
  38. microEye/analysis/fitting/results.py +917 -0
  39. microEye/analysis/fitting/results_stats.py +251 -0
  40. microEye/analysis/fitting/tardis.py +209 -0
  41. microEye/analysis/multi_viewer.py +568 -0
  42. microEye/analysis/processing/__init__.py +1 -0
  43. microEye/analysis/processing/frc.py +120 -0
  44. microEye/analysis/rendering/__init__.py +3 -0
  45. microEye/analysis/rendering/base.py +452 -0
  46. microEye/analysis/rendering/cloud.py +215 -0
  47. microEye/analysis/rendering/core.py +47 -0
  48. microEye/analysis/rendering/volumetric.py +198 -0
  49. microEye/analysis/tools/__init__.py +0 -0
  50. microEye/analysis/tools/kymograms.py +1079 -0
  51. microEye/analysis/tools/roi_selectors.py +556 -0
  52. microEye/analysis/utils/__init__.py +3 -0
  53. microEye/analysis/utils/coordinates.py +18 -0
  54. microEye/analysis/utils/images.py +68 -0
  55. microEye/analysis/utils/windows.py +19 -0
  56. microEye/analysis/viewer/__init__.py +3 -0
  57. microEye/analysis/viewer/image_options_widget.py +615 -0
  58. microEye/analysis/viewer/images.py +1225 -0
  59. microEye/analysis/viewer/layers_widget.py +415 -0
  60. microEye/analysis/viewer/localizations.py +1304 -0
  61. microEye/analysis/viewer/psf.py +1003 -0
  62. microEye/analysis/viewer/volume.py +456 -0
  63. microEye/hardware/__init__.py +3 -0
  64. microEye/hardware/cams/__init__.py +29 -0
  65. microEye/hardware/cams/camera_calibration.py +99 -0
  66. microEye/hardware/cams/camera_list.py +528 -0
  67. microEye/hardware/cams/camera_options.py +694 -0
  68. microEye/hardware/cams/camera_panel.py +941 -0
  69. microEye/hardware/cams/dummy/__init__.py +1 -0
  70. microEye/hardware/cams/dummy/dummy_panel.py +759 -0
  71. microEye/hardware/cams/jobs.py +497 -0
  72. microEye/hardware/cams/line_profiler.py +99 -0
  73. microEye/hardware/cams/linescan/IR_Cam.py +474 -0
  74. microEye/hardware/cams/linescan/__init__.py +1 -0
  75. microEye/hardware/cams/micam.py +524 -0
  76. microEye/hardware/cams/pco/__init__.py +58 -0
  77. microEye/hardware/cams/pco/enums.py +382 -0
  78. microEye/hardware/cams/pco/pco_cam.py +761 -0
  79. microEye/hardware/cams/pco/pco_panel.py +477 -0
  80. microEye/hardware/cams/shortcuts.py +312 -0
  81. microEye/hardware/cams/thorlabs/__init__.py +1 -0
  82. microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
  83. microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
  84. microEye/hardware/cams/ueye/__init__.py +1 -0
  85. microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
  86. microEye/hardware/cams/ueye/ueye_panel.py +861 -0
  87. microEye/hardware/cams/vimba/__init__.py +1 -0
  88. microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
  89. microEye/hardware/cams/vimba/vimba_panel.py +813 -0
  90. microEye/hardware/device.py +60 -0
  91. microEye/hardware/lasers/__init__.py +13 -0
  92. microEye/hardware/lasers/io_matchbox.py +791 -0
  93. microEye/hardware/lasers/io_params.py +85 -0
  94. microEye/hardware/lasers/io_single_laser.py +742 -0
  95. microEye/hardware/lasers/laser_relay.py +594 -0
  96. microEye/hardware/mieye/__init__.py +1 -0
  97. microEye/hardware/mieye/acquisition_manager.py +467 -0
  98. microEye/hardware/mieye/devices_manager.py +533 -0
  99. microEye/hardware/mieye/miEye.py +659 -0
  100. microEye/hardware/misc/__init__.py +0 -0
  101. microEye/hardware/misc/acquisition_view.py +71 -0
  102. microEye/hardware/misc/reglo.py +761 -0
  103. microEye/hardware/misc/temp.py +188 -0
  104. microEye/hardware/port_config.py +59 -0
  105. microEye/hardware/protocols/__init__.py +2 -0
  106. microEye/hardware/protocols/actions.py +402 -0
  107. microEye/hardware/protocols/actions_items.py +703 -0
  108. microEye/hardware/protocols/designer.py +244 -0
  109. microEye/hardware/protocols/scene_manager.py +191 -0
  110. microEye/hardware/protocols/serialization.py +97 -0
  111. microEye/hardware/pycromanager/__init__.py +16 -0
  112. microEye/hardware/pycromanager/core.py +1433 -0
  113. microEye/hardware/pycromanager/devices.py +461 -0
  114. microEye/hardware/pycromanager/enums.py +107 -0
  115. microEye/hardware/pycromanager/headless.py +153 -0
  116. microEye/hardware/pycromanager/utils.py +34 -0
  117. microEye/hardware/pycromanager/widgets/__init__.py +5 -0
  118. microEye/hardware/pycromanager/widgets/bridges.py +407 -0
  119. microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
  120. microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
  121. microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
  122. microEye/hardware/stages/__init__.py +18 -0
  123. microEye/hardware/stages/elliptec/__init__.py +5 -0
  124. microEye/hardware/stages/elliptec/baseDevice.py +314 -0
  125. microEye/hardware/stages/elliptec/device.py +384 -0
  126. microEye/hardware/stages/elliptec/deviceID.py +212 -0
  127. microEye/hardware/stages/elliptec/devicePort.py +379 -0
  128. microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
  129. microEye/hardware/stages/elliptec/devicesView.py +706 -0
  130. microEye/hardware/stages/elliptec/ellDevices.py +134 -0
  131. microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
  132. microEye/hardware/stages/elliptec/motorInfo.py +153 -0
  133. microEye/hardware/stages/elliptec/stage.py +62 -0
  134. microEye/hardware/stages/elliptec/test.py +139 -0
  135. microEye/hardware/stages/kinesis/__init__.py +1 -0
  136. microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
  137. microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
  138. microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
  139. microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
  140. microEye/hardware/stages/kinesis/kinesis.py +776 -0
  141. microEye/hardware/stages/piezo_concept.py +607 -0
  142. microEye/hardware/stages/stabilizer.py +785 -0
  143. microEye/hardware/stages/stage.py +89 -0
  144. microEye/hardware/widgets/__init__.py +10 -0
  145. microEye/hardware/widgets/controller.py +246 -0
  146. microEye/hardware/widgets/devices.py +133 -0
  147. microEye/hardware/widgets/focusWidget.py +264 -0
  148. microEye/hardware/widgets/qlist_slider.py +113 -0
  149. microEye/hardware/widgets/scan_acquisition.py +424 -0
  150. microEye/icons/1024.png +0 -0
  151. microEye/icons/128.png +0 -0
  152. microEye/icons/16.png +0 -0
  153. microEye/icons/24.png +0 -0
  154. microEye/icons/256.png +0 -0
  155. microEye/icons/32.png +0 -0
  156. microEye/icons/48.png +0 -0
  157. microEye/icons/512.png +0 -0
  158. microEye/icons/64.png +0 -0
  159. microEye/icons/__init__.py +0 -0
  160. microEye/icons/close.svg +88 -0
  161. microEye/icons/mieye.png +0 -0
  162. microEye/icons/min.svg +83 -0
  163. microEye/icons/viewer.png +0 -0
  164. microEye/launcher.py +42 -0
  165. microEye/qt.py +181 -0
  166. microEye/utils/__init__.py +2 -0
  167. microEye/utils/enum_encoder.py +10 -0
  168. microEye/utils/expandable_groupbox.py +93 -0
  169. microEye/utils/gui_helper.py +457 -0
  170. microEye/utils/hid/__init__.py +8 -0
  171. microEye/utils/hid/controller.py +153 -0
  172. microEye/utils/hid/device.py +63 -0
  173. microEye/utils/hid/enums.py +140 -0
  174. microEye/utils/hid/utils.py +58 -0
  175. microEye/utils/labelled_slider.py +134 -0
  176. microEye/utils/metadata.py +599 -0
  177. microEye/utils/metadata_tree.py +718 -0
  178. microEye/utils/micro_launcher.py +245 -0
  179. microEye/utils/parameter_tree.py +325 -0
  180. microEye/utils/pyscripting.py +444 -0
  181. microEye/utils/retry_exec.py +35 -0
  182. microEye/utils/start_gui.py +112 -0
  183. microEye/utils/thread_worker.py +115 -0
  184. microEye/utils/uImage.py +1512 -0
  185. microeye-2.3.2.dist-info/METADATA +401 -0
  186. microeye-2.3.2.dist-info/RECORD +190 -0
  187. microeye-2.3.2.dist-info/WHEEL +5 -0
  188. microeye-2.3.2.dist-info/entry_points.txt +2 -0
  189. microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
  190. 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()