bec-widgets 2.10.2__py3-none-any.whl → 2.11.0__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.
- CHANGELOG.md +59 -0
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +5 -5
- bec_widgets/widgets/plots/image/image.py +128 -856
- bec_widgets/widgets/plots/image/image_base.py +1062 -0
- bec_widgets/widgets/plots/image/image_item.py +7 -6
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +8 -11
- bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py +17 -2
- {bec_widgets-2.10.2.dist-info → bec_widgets-2.11.0.dist-info}/METADATA +1 -1
- {bec_widgets-2.10.2.dist-info → bec_widgets-2.11.0.dist-info}/RECORD +14 -13
- pyproject.toml +1 -1
- {bec_widgets-2.10.2.dist-info → bec_widgets-2.11.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.10.2.dist-info → bec_widgets-2.11.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-2.10.2.dist-info → bec_widgets-2.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,34 +1,19 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from collections import defaultdict
|
3
4
|
from typing import Literal
|
4
5
|
|
5
6
|
import numpy as np
|
6
|
-
import pyqtgraph as pg
|
7
7
|
from bec_lib import bec_logger
|
8
8
|
from bec_lib.endpoints import MessageEndpoints
|
9
|
-
from pydantic import
|
10
|
-
from qtpy.
|
11
|
-
from qtpy.QtWidgets import QDialog, QVBoxLayout, QWidget
|
9
|
+
from pydantic import BaseModel, Field, field_validator
|
10
|
+
from qtpy.QtWidgets import QWidget
|
12
11
|
|
13
12
|
from bec_widgets.utils import ConnectionConfig
|
14
13
|
from bec_widgets.utils.colors import Colors
|
15
14
|
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
16
|
-
from bec_widgets.
|
17
|
-
from bec_widgets.utils.toolbar import MaterialIconAction, SwitchableToolBarAction
|
15
|
+
from bec_widgets.widgets.plots.image.image_base import ImageBase
|
18
16
|
from bec_widgets.widgets.plots.image.image_item import ImageItem
|
19
|
-
from bec_widgets.widgets.plots.image.image_roi_plot import ImageROIPlot
|
20
|
-
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
|
21
|
-
from bec_widgets.widgets.plots.image.toolbar_bundles.image_selection import (
|
22
|
-
MonitorSelectionToolbarBundle,
|
23
|
-
)
|
24
|
-
from bec_widgets.widgets.plots.image.toolbar_bundles.processing import ImageProcessingToolbarBundle
|
25
|
-
from bec_widgets.widgets.plots.plot_base import PlotBase
|
26
|
-
from bec_widgets.widgets.plots.roi.image_roi import (
|
27
|
-
BaseROI,
|
28
|
-
CircularROI,
|
29
|
-
RectangularROI,
|
30
|
-
ROIController,
|
31
|
-
)
|
32
17
|
|
33
18
|
logger = bec_logger.logger
|
34
19
|
|
@@ -49,7 +34,15 @@ class ImageConfig(ConnectionConfig):
|
|
49
34
|
_validate_color_map = field_validator("color_map")(Colors.validate_color_map)
|
50
35
|
|
51
36
|
|
52
|
-
class
|
37
|
+
class ImageLayerConfig(BaseModel):
|
38
|
+
monitor: str | None = Field(None, description="The name of the monitor.")
|
39
|
+
monitor_type: Literal["1d", "2d", "auto"] = Field("auto", description="The type of monitor.")
|
40
|
+
source: Literal["device_monitor_1d", "device_monitor_2d", "auto"] = Field(
|
41
|
+
"auto", description="The source of the image data."
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
class Image(ImageBase):
|
53
46
|
"""
|
54
47
|
Image widget for displaying 2D data.
|
55
48
|
"""
|
@@ -93,8 +86,8 @@ class Image(PlotBase):
|
|
93
86
|
# ImageView Specific Settings
|
94
87
|
"color_map",
|
95
88
|
"color_map.setter",
|
96
|
-
"
|
97
|
-
"
|
89
|
+
"v_range",
|
90
|
+
"v_range.setter",
|
98
91
|
"v_min",
|
99
92
|
"v_min.setter",
|
100
93
|
"v_max",
|
@@ -126,8 +119,6 @@ class Image(PlotBase):
|
|
126
119
|
"remove_roi",
|
127
120
|
"rois",
|
128
121
|
]
|
129
|
-
sync_colorbar_with_autorange = Signal()
|
130
|
-
image_updated = Signal()
|
131
122
|
|
132
123
|
def __init__(
|
133
124
|
self,
|
@@ -142,615 +133,15 @@ class Image(PlotBase):
|
|
142
133
|
config = ImageConfig(widget_class=self.__class__.__name__)
|
143
134
|
self.gui_id = config.gui_id
|
144
135
|
self._color_bar = None
|
145
|
-
self.
|
146
|
-
|
147
|
-
|
148
|
-
self.y_roi = None
|
136
|
+
self.subscriptions: defaultdict[str, ImageLayerConfig] = defaultdict(
|
137
|
+
lambda: ImageLayerConfig(monitor=None, monitor_type="auto", source="auto")
|
138
|
+
)
|
149
139
|
super().__init__(
|
150
140
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
151
141
|
)
|
152
|
-
self.
|
153
|
-
|
154
|
-
self.plot_item.addItem(self._main_image)
|
142
|
+
self.layer_removed.connect(self._on_layer_removed)
|
155
143
|
self.scan_id = None
|
156
144
|
|
157
|
-
# Default Color map to plasma
|
158
|
-
self.color_map = "plasma"
|
159
|
-
|
160
|
-
# Initialize ROI plots and side panels
|
161
|
-
self._add_roi_plots()
|
162
|
-
|
163
|
-
self.roi_manager_dialog = None
|
164
|
-
|
165
|
-
# Refresh theme for ROI plots
|
166
|
-
self._update_theme()
|
167
|
-
|
168
|
-
################################################################################
|
169
|
-
# Widget Specific GUI interactions
|
170
|
-
################################################################################
|
171
|
-
def apply_theme(self, theme: str):
|
172
|
-
super().apply_theme(theme)
|
173
|
-
if self.x_roi is not None and self.y_roi is not None:
|
174
|
-
self.x_roi.apply_theme(theme)
|
175
|
-
self.y_roi.apply_theme(theme)
|
176
|
-
|
177
|
-
def _init_toolbar(self):
|
178
|
-
|
179
|
-
# add to the first position
|
180
|
-
self.selection_bundle = MonitorSelectionToolbarBundle(
|
181
|
-
bundle_id="selection", target_widget=self
|
182
|
-
)
|
183
|
-
self.toolbar.add_bundle(bundle=self.selection_bundle, target_widget=self)
|
184
|
-
|
185
|
-
super()._init_toolbar()
|
186
|
-
|
187
|
-
# Image specific changes to PlotBase toolbar
|
188
|
-
self.toolbar.widgets["reset_legend"].action.setVisible(False)
|
189
|
-
|
190
|
-
# ROI Bundle replacement with switchable crosshair
|
191
|
-
self.toolbar.remove_bundle("roi")
|
192
|
-
crosshair = MaterialIconAction(
|
193
|
-
icon_name="point_scan", tooltip="Show Crosshair", checkable=True
|
194
|
-
)
|
195
|
-
crosshair_roi = MaterialIconAction(
|
196
|
-
icon_name="my_location",
|
197
|
-
tooltip="Show Crosshair with ROI plots",
|
198
|
-
checkable=True,
|
199
|
-
parent=self,
|
200
|
-
)
|
201
|
-
crosshair_roi.action.toggled.connect(self.toggle_roi_panels)
|
202
|
-
crosshair.action.toggled.connect(self.toggle_crosshair)
|
203
|
-
switch_crosshair = SwitchableToolBarAction(
|
204
|
-
actions={"crosshair_simple": crosshair, "crosshair_roi": crosshair_roi},
|
205
|
-
initial_action="crosshair_simple",
|
206
|
-
tooltip="Crosshair",
|
207
|
-
checkable=True,
|
208
|
-
parent=self,
|
209
|
-
)
|
210
|
-
self.toolbar.add_action(
|
211
|
-
action_id="switch_crosshair", action=switch_crosshair, target_widget=self
|
212
|
-
)
|
213
|
-
|
214
|
-
# Lock aspect ratio button
|
215
|
-
self.lock_aspect_ratio_action = MaterialIconAction(
|
216
|
-
icon_name="aspect_ratio", tooltip="Lock Aspect Ratio", checkable=True, parent=self
|
217
|
-
)
|
218
|
-
self.toolbar.add_action_to_bundle(
|
219
|
-
bundle_id="mouse_interaction",
|
220
|
-
action_id="lock_aspect_ratio",
|
221
|
-
action=self.lock_aspect_ratio_action,
|
222
|
-
target_widget=self,
|
223
|
-
)
|
224
|
-
self.lock_aspect_ratio_action.action.toggled.connect(
|
225
|
-
lambda checked: self.setProperty("lock_aspect_ratio", checked)
|
226
|
-
)
|
227
|
-
self.lock_aspect_ratio_action.action.setChecked(True)
|
228
|
-
|
229
|
-
self._init_autorange_action()
|
230
|
-
self._init_colorbar_action()
|
231
|
-
|
232
|
-
# Processing Bundle
|
233
|
-
self.processing_bundle = ImageProcessingToolbarBundle(
|
234
|
-
bundle_id="processing", target_widget=self
|
235
|
-
)
|
236
|
-
self.toolbar.add_bundle(self.processing_bundle, target_widget=self)
|
237
|
-
|
238
|
-
def _init_autorange_action(self):
|
239
|
-
|
240
|
-
self.autorange_mean_action = MaterialIconAction(
|
241
|
-
icon_name="hdr_auto", tooltip="Enable Auto Range (Mean)", checkable=True, parent=self
|
242
|
-
)
|
243
|
-
self.autorange_max_action = MaterialIconAction(
|
244
|
-
icon_name="hdr_auto",
|
245
|
-
tooltip="Enable Auto Range (Max)",
|
246
|
-
checkable=True,
|
247
|
-
filled=True,
|
248
|
-
parent=self,
|
249
|
-
)
|
250
|
-
|
251
|
-
self.autorange_switch = SwitchableToolBarAction(
|
252
|
-
actions={
|
253
|
-
"auto_range_mean": self.autorange_mean_action,
|
254
|
-
"auto_range_max": self.autorange_max_action,
|
255
|
-
},
|
256
|
-
initial_action="auto_range_mean",
|
257
|
-
tooltip="Enable Auto Range",
|
258
|
-
checkable=True,
|
259
|
-
parent=self,
|
260
|
-
)
|
261
|
-
|
262
|
-
self.toolbar.add_action(
|
263
|
-
action_id="autorange_image", action=self.autorange_switch, target_widget=self
|
264
|
-
)
|
265
|
-
|
266
|
-
self.autorange_mean_action.action.toggled.connect(
|
267
|
-
lambda checked: self.toggle_autorange(checked, mode="mean")
|
268
|
-
)
|
269
|
-
self.autorange_max_action.action.toggled.connect(
|
270
|
-
lambda checked: self.toggle_autorange(checked, mode="max")
|
271
|
-
)
|
272
|
-
|
273
|
-
self.autorange = True
|
274
|
-
self.autorange_mode = "mean"
|
275
|
-
|
276
|
-
def _init_colorbar_action(self):
|
277
|
-
self.full_colorbar_action = MaterialIconAction(
|
278
|
-
icon_name="edgesensor_low", tooltip="Enable Full Colorbar", checkable=True, parent=self
|
279
|
-
)
|
280
|
-
self.simple_colorbar_action = MaterialIconAction(
|
281
|
-
icon_name="smartphone", tooltip="Enable Simple Colorbar", checkable=True, parent=self
|
282
|
-
)
|
283
|
-
|
284
|
-
self.colorbar_switch = SwitchableToolBarAction(
|
285
|
-
actions={
|
286
|
-
"full_colorbar": self.full_colorbar_action,
|
287
|
-
"simple_colorbar": self.simple_colorbar_action,
|
288
|
-
},
|
289
|
-
initial_action="full_colorbar",
|
290
|
-
tooltip="Enable Full Colorbar",
|
291
|
-
checkable=True,
|
292
|
-
parent=self,
|
293
|
-
)
|
294
|
-
|
295
|
-
self.toolbar.add_action(
|
296
|
-
action_id="switch_colorbar", action=self.colorbar_switch, target_widget=self
|
297
|
-
)
|
298
|
-
|
299
|
-
self.simple_colorbar_action.action.toggled.connect(
|
300
|
-
lambda checked: self.enable_colorbar(checked, style="simple")
|
301
|
-
)
|
302
|
-
self.full_colorbar_action.action.toggled.connect(
|
303
|
-
lambda checked: self.enable_colorbar(checked, style="full")
|
304
|
-
)
|
305
|
-
|
306
|
-
########################################
|
307
|
-
# ROI Gui Manager
|
308
|
-
def add_side_menus(self):
|
309
|
-
super().add_side_menus()
|
310
|
-
|
311
|
-
roi_mgr = ROIPropertyTree(parent=self, image_widget=self)
|
312
|
-
self.side_panel.add_menu(
|
313
|
-
action_id="roi_mgr",
|
314
|
-
icon_name="view_list",
|
315
|
-
tooltip="ROI Manager",
|
316
|
-
widget=roi_mgr,
|
317
|
-
title="ROI Manager",
|
318
|
-
)
|
319
|
-
|
320
|
-
def add_popups(self):
|
321
|
-
super().add_popups() # keep Axis Settings
|
322
|
-
|
323
|
-
roi_action = MaterialIconAction(
|
324
|
-
icon_name="view_list", tooltip="ROI Manager", checkable=True, parent=self
|
325
|
-
)
|
326
|
-
# self.popup_bundle.add_action("roi_mgr", roi_action)
|
327
|
-
self.toolbar.add_action_to_bundle(
|
328
|
-
bundle_id="popup_bundle", action_id="roi_mgr", action=roi_action, target_widget=self
|
329
|
-
)
|
330
|
-
self.toolbar.widgets["roi_mgr"].action.triggered.connect(self.show_roi_manager_popup)
|
331
|
-
|
332
|
-
def show_roi_manager_popup(self):
|
333
|
-
roi_action = self.toolbar.widgets["roi_mgr"].action
|
334
|
-
if self.roi_manager_dialog is None or not self.roi_manager_dialog.isVisible():
|
335
|
-
self.roi_mgr = ROIPropertyTree(parent=self, image_widget=self)
|
336
|
-
self.roi_manager_dialog = QDialog(modal=False)
|
337
|
-
self.roi_manager_dialog.layout = QVBoxLayout(self.roi_manager_dialog)
|
338
|
-
self.roi_manager_dialog.layout.addWidget(self.roi_mgr)
|
339
|
-
self.roi_manager_dialog.finished.connect(self._roi_mgr_closed)
|
340
|
-
self.roi_manager_dialog.show()
|
341
|
-
roi_action.setChecked(True)
|
342
|
-
else:
|
343
|
-
self.roi_manager_dialog.raise_()
|
344
|
-
self.roi_manager_dialog.activateWindow()
|
345
|
-
roi_action.setChecked(True)
|
346
|
-
|
347
|
-
def _roi_mgr_closed(self):
|
348
|
-
self.roi_mgr.close()
|
349
|
-
self.roi_mgr.deleteLater()
|
350
|
-
self.roi_manager_dialog.close()
|
351
|
-
self.roi_manager_dialog.deleteLater()
|
352
|
-
self.roi_manager_dialog = None
|
353
|
-
self.toolbar.widgets["roi_mgr"].action.setChecked(False)
|
354
|
-
|
355
|
-
def enable_colorbar(
|
356
|
-
self,
|
357
|
-
enabled: bool,
|
358
|
-
style: Literal["full", "simple"] = "full",
|
359
|
-
vrange: tuple[int, int] | None = None,
|
360
|
-
):
|
361
|
-
"""
|
362
|
-
Enable the colorbar and switch types of colorbars.
|
363
|
-
|
364
|
-
Args:
|
365
|
-
enabled(bool): Whether to enable the colorbar.
|
366
|
-
style(Literal["full", "simple"]): The type of colorbar to enable.
|
367
|
-
vrange(tuple): The range of values to use for the colorbar.
|
368
|
-
"""
|
369
|
-
autorange_state = self._main_image.autorange
|
370
|
-
if enabled:
|
371
|
-
if self._color_bar:
|
372
|
-
if self.config.color_bar == "full":
|
373
|
-
self.cleanup_histogram_lut_item(self._color_bar)
|
374
|
-
self.plot_widget.removeItem(self._color_bar)
|
375
|
-
self._color_bar = None
|
376
|
-
|
377
|
-
if style == "simple":
|
378
|
-
self._color_bar = pg.ColorBarItem(colorMap=self.config.color_map)
|
379
|
-
self._color_bar.setImageItem(self._main_image)
|
380
|
-
self._color_bar.sigLevelsChangeFinished.connect(
|
381
|
-
lambda: self.setProperty("autorange", False)
|
382
|
-
)
|
383
|
-
|
384
|
-
elif style == "full":
|
385
|
-
self._color_bar = pg.HistogramLUTItem()
|
386
|
-
self._color_bar.setImageItem(self._main_image)
|
387
|
-
self._color_bar.gradient.loadPreset(self.config.color_map)
|
388
|
-
self._color_bar.sigLevelsChanged.connect(
|
389
|
-
lambda: self.setProperty("autorange", False)
|
390
|
-
)
|
391
|
-
|
392
|
-
self.plot_widget.addItem(self._color_bar, row=0, col=1)
|
393
|
-
self.config.color_bar = style
|
394
|
-
else:
|
395
|
-
if self._color_bar:
|
396
|
-
self.plot_widget.removeItem(self._color_bar)
|
397
|
-
self._color_bar = None
|
398
|
-
self.config.color_bar = None
|
399
|
-
|
400
|
-
self.autorange = autorange_state
|
401
|
-
self._sync_colorbar_actions()
|
402
|
-
|
403
|
-
if vrange: # should be at the end to disable the autorange if defined
|
404
|
-
self.v_range = vrange
|
405
|
-
|
406
|
-
################################################################################
|
407
|
-
# Static rois with roi manager
|
408
|
-
|
409
|
-
def add_roi(
|
410
|
-
self,
|
411
|
-
kind: Literal["rect", "circle"] = "rect",
|
412
|
-
name: str | None = None,
|
413
|
-
line_width: int | None = 5,
|
414
|
-
pos: tuple[float, float] | None = (10, 10),
|
415
|
-
size: tuple[float, float] | None = (50, 50),
|
416
|
-
**pg_kwargs,
|
417
|
-
) -> RectangularROI | CircularROI:
|
418
|
-
"""
|
419
|
-
Add a ROI to the image.
|
420
|
-
|
421
|
-
Args:
|
422
|
-
kind(str): The type of ROI to add. Options are "rect" or "circle".
|
423
|
-
name(str): The name of the ROI.
|
424
|
-
line_width(int): The line width of the ROI.
|
425
|
-
pos(tuple): The position of the ROI.
|
426
|
-
size(tuple): The size of the ROI.
|
427
|
-
**pg_kwargs: Additional arguments for the ROI.
|
428
|
-
|
429
|
-
Returns:
|
430
|
-
RectangularROI | CircularROI: The created ROI object.
|
431
|
-
"""
|
432
|
-
if name is None:
|
433
|
-
name = f"ROI_{len(self.roi_controller.rois) + 1}"
|
434
|
-
if kind == "rect":
|
435
|
-
roi = RectangularROI(
|
436
|
-
pos=pos,
|
437
|
-
size=size,
|
438
|
-
parent_image=self,
|
439
|
-
line_width=line_width,
|
440
|
-
label=name,
|
441
|
-
**pg_kwargs,
|
442
|
-
)
|
443
|
-
elif kind == "circle":
|
444
|
-
roi = CircularROI(
|
445
|
-
pos=pos,
|
446
|
-
size=size,
|
447
|
-
parent_image=self,
|
448
|
-
line_width=line_width,
|
449
|
-
label=name,
|
450
|
-
**pg_kwargs,
|
451
|
-
)
|
452
|
-
else:
|
453
|
-
raise ValueError("kind must be 'rect' or 'circle'")
|
454
|
-
|
455
|
-
# Add to plot and controller (controller assigns color)
|
456
|
-
self.plot_item.addItem(roi)
|
457
|
-
self.roi_controller.add_roi(roi)
|
458
|
-
roi.add_scale_handle()
|
459
|
-
return roi
|
460
|
-
|
461
|
-
def remove_roi(self, roi: int | str):
|
462
|
-
"""Remove an ROI by index or label via the ROIController."""
|
463
|
-
if isinstance(roi, int):
|
464
|
-
self.roi_controller.remove_roi_by_index(roi)
|
465
|
-
elif isinstance(roi, str):
|
466
|
-
self.roi_controller.remove_roi_by_name(roi)
|
467
|
-
else:
|
468
|
-
raise ValueError("roi must be an int index or str name")
|
469
|
-
|
470
|
-
def _add_roi_plots(self):
|
471
|
-
"""
|
472
|
-
Initialize the ROI plots and side panels.
|
473
|
-
"""
|
474
|
-
# Create ROI plot widgets
|
475
|
-
self.x_roi = ImageROIPlot(parent=self)
|
476
|
-
self.y_roi = ImageROIPlot(parent=self)
|
477
|
-
self.x_roi.apply_theme("dark")
|
478
|
-
self.y_roi.apply_theme("dark")
|
479
|
-
|
480
|
-
# Set titles for the plots
|
481
|
-
self.x_roi.plot_item.setTitle("X ROI")
|
482
|
-
self.y_roi.plot_item.setTitle("Y ROI")
|
483
|
-
|
484
|
-
# Create side panels
|
485
|
-
self.side_panel_x = SidePanel(
|
486
|
-
parent=self, orientation="bottom", panel_max_width=200, show_toolbar=False
|
487
|
-
)
|
488
|
-
self.side_panel_y = SidePanel(
|
489
|
-
parent=self, orientation="left", panel_max_width=200, show_toolbar=False
|
490
|
-
)
|
491
|
-
|
492
|
-
# Add ROI plots to side panels
|
493
|
-
self.x_panel_index = self.side_panel_x.add_menu(widget=self.x_roi)
|
494
|
-
self.y_panel_index = self.side_panel_y.add_menu(widget=self.y_roi)
|
495
|
-
|
496
|
-
# # Add side panels to the layout
|
497
|
-
self.layout_manager.add_widget_relative(
|
498
|
-
self.side_panel_x, self.round_plot_widget, position="bottom", shift_direction="down"
|
499
|
-
)
|
500
|
-
self.layout_manager.add_widget_relative(
|
501
|
-
self.side_panel_y, self.round_plot_widget, position="left", shift_direction="right"
|
502
|
-
)
|
503
|
-
|
504
|
-
def toggle_roi_panels(self, checked: bool):
|
505
|
-
"""
|
506
|
-
Show or hide the ROI panels based on the test action toggle state.
|
507
|
-
|
508
|
-
Args:
|
509
|
-
checked (bool): Whether the test action is checked.
|
510
|
-
"""
|
511
|
-
if checked:
|
512
|
-
# Show the ROI panels
|
513
|
-
self.hook_crosshair()
|
514
|
-
self.side_panel_x.show_panel(self.x_panel_index)
|
515
|
-
self.side_panel_y.show_panel(self.y_panel_index)
|
516
|
-
self.crosshair.coordinatesChanged2D.connect(self.update_image_slices)
|
517
|
-
self.image_updated.connect(self.update_image_slices)
|
518
|
-
else:
|
519
|
-
self.unhook_crosshair()
|
520
|
-
# Hide the ROI panels
|
521
|
-
self.side_panel_x.hide_panel()
|
522
|
-
self.side_panel_y.hide_panel()
|
523
|
-
self.image_updated.disconnect(self.update_image_slices)
|
524
|
-
|
525
|
-
@SafeSlot()
|
526
|
-
def update_image_slices(self, coordinates: tuple[int, int, int] = None):
|
527
|
-
"""
|
528
|
-
Update the image slices based on the crosshair position.
|
529
|
-
|
530
|
-
Args:
|
531
|
-
coordinates(tuple): The coordinates of the crosshair.
|
532
|
-
"""
|
533
|
-
if coordinates is None:
|
534
|
-
# Try to get coordinates from crosshair position (like in crosshair mouse_moved)
|
535
|
-
if (
|
536
|
-
hasattr(self, "crosshair")
|
537
|
-
and hasattr(self.crosshair, "v_line")
|
538
|
-
and hasattr(self.crosshair, "h_line")
|
539
|
-
):
|
540
|
-
x = int(round(self.crosshair.v_line.value()))
|
541
|
-
y = int(round(self.crosshair.h_line.value()))
|
542
|
-
else:
|
543
|
-
return
|
544
|
-
else:
|
545
|
-
x = coordinates[1]
|
546
|
-
y = coordinates[2]
|
547
|
-
image = self._main_image.image
|
548
|
-
if image is None:
|
549
|
-
return
|
550
|
-
max_row, max_col = image.shape[0] - 1, image.shape[1] - 1
|
551
|
-
row, col = x, y
|
552
|
-
if not (0 <= row <= max_row and 0 <= col <= max_col):
|
553
|
-
return
|
554
|
-
# Horizontal slice
|
555
|
-
h_slice = image[:, col]
|
556
|
-
x_axis = np.arange(h_slice.shape[0])
|
557
|
-
self.x_roi.plot_item.clear()
|
558
|
-
self.x_roi.plot_item.plot(x_axis, h_slice, pen=pg.mkPen(self.x_roi.curve_color, width=3))
|
559
|
-
# Vertical slice
|
560
|
-
v_slice = image[row, :]
|
561
|
-
y_axis = np.arange(v_slice.shape[0])
|
562
|
-
self.y_roi.plot_item.clear()
|
563
|
-
self.y_roi.plot_item.plot(v_slice, y_axis, pen=pg.mkPen(self.y_roi.curve_color, width=3))
|
564
|
-
|
565
|
-
################################################################################
|
566
|
-
# Widget Specific Properties
|
567
|
-
################################################################################
|
568
|
-
################################################################################
|
569
|
-
# Rois
|
570
|
-
|
571
|
-
@property
|
572
|
-
def rois(self) -> list[BaseROI]:
|
573
|
-
"""
|
574
|
-
Get the list of ROIs.
|
575
|
-
"""
|
576
|
-
return self.roi_controller.rois
|
577
|
-
|
578
|
-
################################################################################
|
579
|
-
# Colorbar toggle
|
580
|
-
|
581
|
-
@SafeProperty(bool)
|
582
|
-
def enable_simple_colorbar(self) -> bool:
|
583
|
-
"""
|
584
|
-
Enable the simple colorbar.
|
585
|
-
"""
|
586
|
-
enabled = False
|
587
|
-
if self.config.color_bar == "simple":
|
588
|
-
enabled = True
|
589
|
-
return enabled
|
590
|
-
|
591
|
-
@enable_simple_colorbar.setter
|
592
|
-
def enable_simple_colorbar(self, value: bool):
|
593
|
-
"""
|
594
|
-
Enable the simple colorbar.
|
595
|
-
|
596
|
-
Args:
|
597
|
-
value(bool): Whether to enable the simple colorbar.
|
598
|
-
"""
|
599
|
-
self.enable_colorbar(enabled=value, style="simple")
|
600
|
-
|
601
|
-
@SafeProperty(bool)
|
602
|
-
def enable_full_colorbar(self) -> bool:
|
603
|
-
"""
|
604
|
-
Enable the full colorbar.
|
605
|
-
"""
|
606
|
-
enabled = False
|
607
|
-
if self.config.color_bar == "full":
|
608
|
-
enabled = True
|
609
|
-
return enabled
|
610
|
-
|
611
|
-
@enable_full_colorbar.setter
|
612
|
-
def enable_full_colorbar(self, value: bool):
|
613
|
-
"""
|
614
|
-
Enable the full colorbar.
|
615
|
-
|
616
|
-
Args:
|
617
|
-
value(bool): Whether to enable the full colorbar.
|
618
|
-
"""
|
619
|
-
self.enable_colorbar(enabled=value, style="full")
|
620
|
-
|
621
|
-
################################################################################
|
622
|
-
# Appearance
|
623
|
-
|
624
|
-
@SafeProperty(str)
|
625
|
-
def color_map(self) -> str:
|
626
|
-
"""
|
627
|
-
Set the color map of the image.
|
628
|
-
"""
|
629
|
-
return self.config.color_map
|
630
|
-
|
631
|
-
@color_map.setter
|
632
|
-
def color_map(self, value: str):
|
633
|
-
"""
|
634
|
-
Set the color map of the image.
|
635
|
-
|
636
|
-
Args:
|
637
|
-
value(str): The color map to set.
|
638
|
-
"""
|
639
|
-
try:
|
640
|
-
self.config.color_map = value
|
641
|
-
self._main_image.color_map = value
|
642
|
-
|
643
|
-
if self._color_bar:
|
644
|
-
if self.config.color_bar == "simple":
|
645
|
-
self._color_bar.setColorMap(value)
|
646
|
-
elif self.config.color_bar == "full":
|
647
|
-
self._color_bar.gradient.loadPreset(value)
|
648
|
-
except ValidationError:
|
649
|
-
return
|
650
|
-
|
651
|
-
# v_range is for designer, vrange is for RPC
|
652
|
-
@SafeProperty("QPointF")
|
653
|
-
def v_range(self) -> QPointF:
|
654
|
-
"""
|
655
|
-
Set the v_range of the main image.
|
656
|
-
"""
|
657
|
-
vmin, vmax = self._main_image.v_range
|
658
|
-
return QPointF(vmin, vmax)
|
659
|
-
|
660
|
-
@v_range.setter
|
661
|
-
def v_range(self, value: tuple | list | QPointF):
|
662
|
-
"""
|
663
|
-
Set the v_range of the main image.
|
664
|
-
|
665
|
-
Args:
|
666
|
-
value(tuple | list | QPointF): The range of values to set.
|
667
|
-
"""
|
668
|
-
if isinstance(value, (tuple, list)):
|
669
|
-
value = self._tuple_to_qpointf(value)
|
670
|
-
|
671
|
-
vmin, vmax = value.x(), value.y()
|
672
|
-
|
673
|
-
self._main_image.v_range = (vmin, vmax)
|
674
|
-
|
675
|
-
# propagate to colorbar if exists
|
676
|
-
if self._color_bar:
|
677
|
-
if self.config.color_bar == "simple":
|
678
|
-
self._color_bar.setLevels(low=vmin, high=vmax)
|
679
|
-
elif self.config.color_bar == "full":
|
680
|
-
self._color_bar.setLevels(min=vmin, max=vmax)
|
681
|
-
self._color_bar.setHistogramRange(vmin - 0.1 * vmin, vmax + 0.1 * vmax)
|
682
|
-
|
683
|
-
self.autorange_switch.set_state_all(False)
|
684
|
-
|
685
|
-
@property
|
686
|
-
def vrange(self) -> tuple:
|
687
|
-
"""
|
688
|
-
Get the vrange of the image.
|
689
|
-
"""
|
690
|
-
return (self.v_range.x(), self.v_range.y())
|
691
|
-
|
692
|
-
@vrange.setter
|
693
|
-
def vrange(self, value):
|
694
|
-
"""
|
695
|
-
Set the vrange of the image.
|
696
|
-
|
697
|
-
Args:
|
698
|
-
value(tuple):
|
699
|
-
"""
|
700
|
-
self.v_range = value
|
701
|
-
|
702
|
-
@property
|
703
|
-
def v_min(self) -> float:
|
704
|
-
"""
|
705
|
-
Get the minimum value of the v_range.
|
706
|
-
"""
|
707
|
-
return self.v_range.x()
|
708
|
-
|
709
|
-
@v_min.setter
|
710
|
-
def v_min(self, value: float):
|
711
|
-
"""
|
712
|
-
Set the minimum value of the v_range.
|
713
|
-
|
714
|
-
Args:
|
715
|
-
value(float): The minimum value to set.
|
716
|
-
"""
|
717
|
-
self.v_range = (value, self.v_range.y())
|
718
|
-
|
719
|
-
@property
|
720
|
-
def v_max(self) -> float:
|
721
|
-
"""
|
722
|
-
Get the maximum value of the v_range.
|
723
|
-
"""
|
724
|
-
return self.v_range.y()
|
725
|
-
|
726
|
-
@v_max.setter
|
727
|
-
def v_max(self, value: float):
|
728
|
-
"""
|
729
|
-
Set the maximum value of the v_range.
|
730
|
-
|
731
|
-
Args:
|
732
|
-
value(float): The maximum value to set.
|
733
|
-
"""
|
734
|
-
self.v_range = (self.v_range.x(), value)
|
735
|
-
|
736
|
-
@SafeProperty(bool)
|
737
|
-
def lock_aspect_ratio(self) -> bool:
|
738
|
-
"""
|
739
|
-
Whether the aspect ratio is locked.
|
740
|
-
"""
|
741
|
-
return self.config.lock_aspect_ratio
|
742
|
-
|
743
|
-
@lock_aspect_ratio.setter
|
744
|
-
def lock_aspect_ratio(self, value: bool):
|
745
|
-
"""
|
746
|
-
Set the aspect ratio lock.
|
747
|
-
|
748
|
-
Args:
|
749
|
-
value(bool): Whether to lock the aspect ratio.
|
750
|
-
"""
|
751
|
-
self.config.lock_aspect_ratio = bool(value)
|
752
|
-
self.plot_item.setAspectLocked(value)
|
753
|
-
|
754
145
|
################################################################################
|
755
146
|
# Data Acquisition
|
756
147
|
|
@@ -759,7 +150,7 @@ class Image(PlotBase):
|
|
759
150
|
"""
|
760
151
|
The name of the monitor to use for the image.
|
761
152
|
"""
|
762
|
-
return self.
|
153
|
+
return self.subscriptions["main"].monitor or ""
|
763
154
|
|
764
155
|
@monitor.setter
|
765
156
|
def monitor(self, value: str):
|
@@ -769,7 +160,7 @@ class Image(PlotBase):
|
|
769
160
|
Args:
|
770
161
|
value(str): The name of the monitor to set.
|
771
162
|
"""
|
772
|
-
if self.
|
163
|
+
if self.subscriptions["main"].monitor == value:
|
773
164
|
return
|
774
165
|
try:
|
775
166
|
self.entry_validator.validate_monitor(value)
|
@@ -780,103 +171,95 @@ class Image(PlotBase):
|
|
780
171
|
@property
|
781
172
|
def main_image(self) -> ImageItem:
|
782
173
|
"""Access the main image item."""
|
783
|
-
return self.
|
174
|
+
return self.layer_manager["main"].image
|
784
175
|
|
785
176
|
################################################################################
|
786
|
-
#
|
787
|
-
|
788
|
-
@
|
789
|
-
def
|
790
|
-
|
791
|
-
|
792
|
-
"""
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
177
|
+
# High Level methods for API
|
178
|
+
################################################################################
|
179
|
+
@SafeSlot(popup_error=True)
|
180
|
+
def image(
|
181
|
+
self,
|
182
|
+
monitor: str | None = None,
|
183
|
+
monitor_type: Literal["auto", "1d", "2d"] = "auto",
|
184
|
+
color_map: str | None = None,
|
185
|
+
color_bar: Literal["simple", "full"] | None = None,
|
186
|
+
vrange: tuple[int, int] | None = None,
|
187
|
+
) -> ImageItem:
|
797
188
|
"""
|
798
|
-
Set
|
189
|
+
Set the image source and update the image.
|
799
190
|
|
800
191
|
Args:
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
self._sync_colorbar_levels()
|
807
|
-
self._sync_autorange_switch()
|
808
|
-
|
809
|
-
@SafeProperty(str)
|
810
|
-
def autorange_mode(self) -> str:
|
811
|
-
"""
|
812
|
-
Autorange mode.
|
813
|
-
|
814
|
-
Options:
|
815
|
-
- "max": Use the maximum value of the image for autoranging.
|
816
|
-
- "mean": Use the mean value of the image for autoranging.
|
192
|
+
monitor(str): The name of the monitor to use for the image.
|
193
|
+
monitor_type(str): The type of monitor to use. Options are "1d", "2d", or "auto".
|
194
|
+
color_map(str): The color map to use for the image.
|
195
|
+
color_bar(str): The type of color bar to use. Options are "simple" or "full".
|
196
|
+
vrange(tuple): The range of values to use for the color map.
|
817
197
|
|
198
|
+
Returns:
|
199
|
+
ImageItem: The image object.
|
818
200
|
"""
|
819
|
-
return self._main_image.autorange_mode
|
820
201
|
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
202
|
+
if self.subscriptions["main"].monitor:
|
203
|
+
self.disconnect_monitor(self.subscriptions["main"].monitor)
|
204
|
+
self.entry_validator.validate_monitor(monitor)
|
205
|
+
self.subscriptions["main"].monitor = monitor
|
825
206
|
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
207
|
+
if monitor_type == "1d":
|
208
|
+
self.subscriptions["main"].source = "device_monitor_1d"
|
209
|
+
self.subscriptions["main"].monitor_type = "1d"
|
210
|
+
elif monitor_type == "2d":
|
211
|
+
self.subscriptions["main"].source = "device_monitor_2d"
|
212
|
+
self.subscriptions["main"].monitor_type = "2d"
|
213
|
+
elif monitor_type == "auto":
|
214
|
+
self.subscriptions["main"].source = "auto"
|
215
|
+
logger.warning(
|
216
|
+
f"Updates for '{monitor}' will be fetch from both 1D and 2D monitor endpoints."
|
217
|
+
)
|
218
|
+
self.subscriptions["main"].monitor_type = "auto"
|
833
219
|
|
834
|
-
self.
|
220
|
+
self.set_image_update(monitor=monitor, type=monitor_type)
|
221
|
+
if color_map is not None:
|
222
|
+
self.main_image.color_map = color_map
|
223
|
+
if color_bar is not None:
|
224
|
+
self.enable_colorbar(True, color_bar)
|
225
|
+
if vrange is not None:
|
226
|
+
self.vrange = vrange
|
835
227
|
|
836
|
-
|
837
|
-
def toggle_autorange(self, enabled: bool, mode: str):
|
838
|
-
"""
|
839
|
-
Toggle autorange.
|
228
|
+
self._sync_device_selection()
|
840
229
|
|
841
|
-
|
842
|
-
enabled(bool): Whether to enable autorange.
|
843
|
-
mode(str): The autorange mode. Options are "max" or "mean".
|
844
|
-
"""
|
845
|
-
if self._main_image is not None:
|
846
|
-
self._main_image.autorange = enabled
|
847
|
-
self._main_image.autorange_mode = mode
|
848
|
-
if enabled:
|
849
|
-
self._main_image.apply_autorange()
|
850
|
-
self._sync_colorbar_levels()
|
851
|
-
|
852
|
-
def _sync_autorange_switch(self):
|
853
|
-
"""
|
854
|
-
Synchronize the autorange switch with the current autorange state and mode if changed from outside.
|
855
|
-
"""
|
856
|
-
self.autorange_switch.block_all_signals(True)
|
857
|
-
self.autorange_switch.set_default_action(f"auto_range_{self._main_image.autorange_mode}")
|
858
|
-
self.autorange_switch.set_state_all(self._main_image.autorange)
|
859
|
-
self.autorange_switch.block_all_signals(False)
|
860
|
-
|
861
|
-
def _sync_colorbar_levels(self):
|
862
|
-
"""Immediately propagate current levels to the active colorbar."""
|
863
|
-
vrange = self._main_image.v_range
|
864
|
-
if self._color_bar:
|
865
|
-
self._color_bar.blockSignals(True)
|
866
|
-
self.v_range = vrange
|
867
|
-
self._color_bar.blockSignals(False)
|
230
|
+
return self.main_image
|
868
231
|
|
869
|
-
def
|
232
|
+
def _sync_device_selection(self):
|
870
233
|
"""
|
871
|
-
Synchronize the
|
234
|
+
Synchronize the device selection with the current monitor.
|
872
235
|
"""
|
873
|
-
self.
|
874
|
-
if
|
875
|
-
|
876
|
-
|
236
|
+
config = self.subscriptions["main"]
|
237
|
+
if config.monitor is not None:
|
238
|
+
for combo in (
|
239
|
+
self.selection_bundle.device_combo_box,
|
240
|
+
self.selection_bundle.dim_combo_box,
|
241
|
+
):
|
242
|
+
combo.blockSignals(True)
|
243
|
+
self.selection_bundle.device_combo_box.set_device(config.monitor)
|
244
|
+
self.selection_bundle.dim_combo_box.setCurrentText(config.monitor_type)
|
245
|
+
for combo in (
|
246
|
+
self.selection_bundle.device_combo_box,
|
247
|
+
self.selection_bundle.dim_combo_box,
|
248
|
+
):
|
249
|
+
combo.blockSignals(False)
|
877
250
|
else:
|
878
|
-
|
879
|
-
|
251
|
+
for combo in (
|
252
|
+
self.selection_bundle.device_combo_box,
|
253
|
+
self.selection_bundle.dim_combo_box,
|
254
|
+
):
|
255
|
+
combo.blockSignals(True)
|
256
|
+
self.selection_bundle.device_combo_box.setCurrentText("")
|
257
|
+
self.selection_bundle.dim_combo_box.setCurrentText("auto")
|
258
|
+
for combo in (
|
259
|
+
self.selection_bundle.device_combo_box,
|
260
|
+
self.selection_bundle.dim_combo_box,
|
261
|
+
):
|
262
|
+
combo.blockSignals(False)
|
880
263
|
|
881
264
|
################################################################################
|
882
265
|
# Post Processing
|
@@ -887,7 +270,7 @@ class Image(PlotBase):
|
|
887
270
|
"""
|
888
271
|
Whether FFT postprocessing is enabled.
|
889
272
|
"""
|
890
|
-
return self.
|
273
|
+
return self.main_image.fft
|
891
274
|
|
892
275
|
@fft.setter
|
893
276
|
def fft(self, enable: bool):
|
@@ -897,14 +280,14 @@ class Image(PlotBase):
|
|
897
280
|
Args:
|
898
281
|
enable(bool): Whether to enable FFT postprocessing.
|
899
282
|
"""
|
900
|
-
self.
|
283
|
+
self.main_image.fft = enable
|
901
284
|
|
902
285
|
@SafeProperty(bool)
|
903
286
|
def log(self) -> bool:
|
904
287
|
"""
|
905
288
|
Whether logarithmic scaling is applied.
|
906
289
|
"""
|
907
|
-
return self.
|
290
|
+
return self.main_image.log
|
908
291
|
|
909
292
|
@log.setter
|
910
293
|
def log(self, enable: bool):
|
@@ -914,14 +297,14 @@ class Image(PlotBase):
|
|
914
297
|
Args:
|
915
298
|
enable(bool): Whether to enable logarithmic scaling.
|
916
299
|
"""
|
917
|
-
self.
|
300
|
+
self.main_image.log = enable
|
918
301
|
|
919
302
|
@SafeProperty(int)
|
920
303
|
def num_rotation_90(self) -> int:
|
921
304
|
"""
|
922
305
|
The number of 90° rotations to apply counterclockwise.
|
923
306
|
"""
|
924
|
-
return self.
|
307
|
+
return self.main_image.num_rotation_90
|
925
308
|
|
926
309
|
@num_rotation_90.setter
|
927
310
|
def num_rotation_90(self, value: int):
|
@@ -931,14 +314,14 @@ class Image(PlotBase):
|
|
931
314
|
Args:
|
932
315
|
value(int): The number of 90° rotations to apply.
|
933
316
|
"""
|
934
|
-
self.
|
317
|
+
self.main_image.num_rotation_90 = value
|
935
318
|
|
936
319
|
@SafeProperty(bool)
|
937
320
|
def transpose(self) -> bool:
|
938
321
|
"""
|
939
322
|
Whether the image is transposed.
|
940
323
|
"""
|
941
|
-
return self.
|
324
|
+
return self.main_image.transpose
|
942
325
|
|
943
326
|
@transpose.setter
|
944
327
|
def transpose(self, enable: bool):
|
@@ -948,94 +331,7 @@ class Image(PlotBase):
|
|
948
331
|
Args:
|
949
332
|
enable(bool): Whether to enable transposing the image.
|
950
333
|
"""
|
951
|
-
self.
|
952
|
-
|
953
|
-
################################################################################
|
954
|
-
# High Level methods for API
|
955
|
-
################################################################################
|
956
|
-
@SafeSlot(popup_error=True)
|
957
|
-
def image(
|
958
|
-
self,
|
959
|
-
monitor: str | None = None,
|
960
|
-
monitor_type: Literal["auto", "1d", "2d"] = "auto",
|
961
|
-
color_map: str | None = None,
|
962
|
-
color_bar: Literal["simple", "full"] | None = None,
|
963
|
-
vrange: tuple[int, int] | None = None,
|
964
|
-
) -> ImageItem:
|
965
|
-
"""
|
966
|
-
Set the image source and update the image.
|
967
|
-
|
968
|
-
Args:
|
969
|
-
monitor(str): The name of the monitor to use for the image.
|
970
|
-
monitor_type(str): The type of monitor to use. Options are "1d", "2d", or "auto".
|
971
|
-
color_map(str): The color map to use for the image.
|
972
|
-
color_bar(str): The type of color bar to use. Options are "simple" or "full".
|
973
|
-
vrange(tuple): The range of values to use for the color map.
|
974
|
-
|
975
|
-
Returns:
|
976
|
-
ImageItem: The image object.
|
977
|
-
"""
|
978
|
-
|
979
|
-
if self._main_image.config.monitor is not None:
|
980
|
-
self.disconnect_monitor(self._main_image.config.monitor)
|
981
|
-
self.entry_validator.validate_monitor(monitor)
|
982
|
-
self._main_image.config.monitor = monitor
|
983
|
-
|
984
|
-
if monitor_type == "1d":
|
985
|
-
self._main_image.config.source = "device_monitor_1d"
|
986
|
-
self._main_image.config.monitor_type = "1d"
|
987
|
-
elif monitor_type == "2d":
|
988
|
-
self._main_image.config.source = "device_monitor_2d"
|
989
|
-
self._main_image.config.monitor_type = "2d"
|
990
|
-
elif monitor_type == "auto":
|
991
|
-
self._main_image.config.source = "auto"
|
992
|
-
logger.warning(
|
993
|
-
f"Updates for '{monitor}' will be fetch from both 1D and 2D monitor endpoints."
|
994
|
-
)
|
995
|
-
self._main_image.config.monitor_type = "auto"
|
996
|
-
|
997
|
-
self.set_image_update(monitor=monitor, type=monitor_type)
|
998
|
-
if color_map is not None:
|
999
|
-
self._main_image.color_map = color_map
|
1000
|
-
if color_bar is not None:
|
1001
|
-
self.enable_colorbar(True, color_bar)
|
1002
|
-
if vrange is not None:
|
1003
|
-
self.vrange = vrange
|
1004
|
-
|
1005
|
-
self._sync_device_selection()
|
1006
|
-
|
1007
|
-
return self._main_image
|
1008
|
-
|
1009
|
-
def _sync_device_selection(self):
|
1010
|
-
"""
|
1011
|
-
Synchronize the device selection with the current monitor.
|
1012
|
-
"""
|
1013
|
-
if self._main_image.config.monitor is not None:
|
1014
|
-
for combo in (
|
1015
|
-
self.selection_bundle.device_combo_box,
|
1016
|
-
self.selection_bundle.dim_combo_box,
|
1017
|
-
):
|
1018
|
-
combo.blockSignals(True)
|
1019
|
-
self.selection_bundle.device_combo_box.set_device(self._main_image.config.monitor)
|
1020
|
-
self.selection_bundle.dim_combo_box.setCurrentText(self._main_image.config.monitor_type)
|
1021
|
-
for combo in (
|
1022
|
-
self.selection_bundle.device_combo_box,
|
1023
|
-
self.selection_bundle.dim_combo_box,
|
1024
|
-
):
|
1025
|
-
combo.blockSignals(False)
|
1026
|
-
else:
|
1027
|
-
for combo in (
|
1028
|
-
self.selection_bundle.device_combo_box,
|
1029
|
-
self.selection_bundle.dim_combo_box,
|
1030
|
-
):
|
1031
|
-
combo.blockSignals(True)
|
1032
|
-
self.selection_bundle.device_combo_box.setCurrentText("")
|
1033
|
-
self.selection_bundle.dim_combo_box.setCurrentText("auto")
|
1034
|
-
for combo in (
|
1035
|
-
self.selection_bundle.device_combo_box,
|
1036
|
-
self.selection_bundle.dim_combo_box,
|
1037
|
-
):
|
1038
|
-
combo.blockSignals(False)
|
334
|
+
self.main_image.transpose = enable
|
1039
335
|
|
1040
336
|
################################################################################
|
1041
337
|
# Image Update Methods
|
@@ -1069,8 +365,8 @@ class Image(PlotBase):
|
|
1069
365
|
self.bec_dispatcher.connect_slot(
|
1070
366
|
self.on_image_update_2d, MessageEndpoints.device_monitor_2d(monitor)
|
1071
367
|
)
|
1072
|
-
|
1073
|
-
self.
|
368
|
+
logger.info(f"Connected to {monitor} with type {type}")
|
369
|
+
self.subscriptions["main"].monitor = monitor
|
1074
370
|
|
1075
371
|
def disconnect_monitor(self, monitor: str):
|
1076
372
|
"""
|
@@ -1085,7 +381,7 @@ class Image(PlotBase):
|
|
1085
381
|
self.bec_dispatcher.disconnect_slot(
|
1086
382
|
self.on_image_update_2d, MessageEndpoints.device_monitor_2d(monitor)
|
1087
383
|
)
|
1088
|
-
self.
|
384
|
+
self.subscriptions["main"].monitor = None
|
1089
385
|
self._sync_device_selection()
|
1090
386
|
|
1091
387
|
########################################
|
@@ -1107,13 +403,13 @@ class Image(PlotBase):
|
|
1107
403
|
return
|
1108
404
|
if current_scan_id != self.scan_id:
|
1109
405
|
self.scan_id = current_scan_id
|
1110
|
-
self.
|
1111
|
-
self.
|
1112
|
-
self.
|
1113
|
-
image_buffer = self.adjust_image_buffer(self.
|
406
|
+
self.main_image.clear()
|
407
|
+
self.main_image.buffer = []
|
408
|
+
self.main_image.max_len = 0
|
409
|
+
image_buffer = self.adjust_image_buffer(self.main_image, data)
|
1114
410
|
if self._color_bar is not None:
|
1115
411
|
self._color_bar.blockSignals(True)
|
1116
|
-
self.
|
412
|
+
self.main_image.set_data(image_buffer)
|
1117
413
|
if self._color_bar is not None:
|
1118
414
|
self._color_bar.blockSignals(False)
|
1119
415
|
self.image_updated.emit()
|
@@ -1165,7 +461,7 @@ class Image(PlotBase):
|
|
1165
461
|
data = msg["data"]
|
1166
462
|
if self._color_bar is not None:
|
1167
463
|
self._color_bar.blockSignals(True)
|
1168
|
-
self.
|
464
|
+
self.main_image.set_data(data)
|
1169
465
|
if self._color_bar is not None:
|
1170
466
|
self._color_bar.blockSignals(False)
|
1171
467
|
self.image_updated.emit()
|
@@ -1174,60 +470,36 @@ class Image(PlotBase):
|
|
1174
470
|
# Clean up
|
1175
471
|
################################################################################
|
1176
472
|
|
1177
|
-
@
|
1178
|
-
def
|
473
|
+
@SafeSlot(str)
|
474
|
+
def _on_layer_removed(self, layer_name: str):
|
1179
475
|
"""
|
1180
|
-
|
476
|
+
Handle the removal of a layer by disconnecting the monitor.
|
1181
477
|
|
1182
478
|
Args:
|
1183
|
-
|
479
|
+
layer_name(str): The name of the layer that was removed.
|
1184
480
|
"""
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
histogram_lut_item.gradient.colorDialog.deleteLater()
|
481
|
+
if layer_name not in self.subscriptions:
|
482
|
+
return
|
483
|
+
config = self.subscriptions[layer_name]
|
484
|
+
if config.monitor is not None:
|
485
|
+
self.disconnect_monitor(config.monitor)
|
486
|
+
config.monitor = None
|
1192
487
|
|
1193
488
|
def cleanup(self):
|
1194
489
|
"""
|
1195
490
|
Disconnect the image update signals and clean up the image.
|
1196
491
|
"""
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
self.disconnect_monitor(self._main_image.config.monitor)
|
1205
|
-
self._main_image.config.monitor = None
|
1206
|
-
self.plot_item.removeItem(self._main_image)
|
1207
|
-
self._main_image = None
|
1208
|
-
|
1209
|
-
# Colorbar Cleanup
|
1210
|
-
if self._color_bar:
|
1211
|
-
if self.config.color_bar == "full":
|
1212
|
-
self.cleanup_histogram_lut_item(self._color_bar)
|
1213
|
-
if self.config.color_bar == "simple":
|
1214
|
-
self.plot_widget.removeItem(self._color_bar)
|
1215
|
-
self._color_bar.deleteLater()
|
1216
|
-
self._color_bar = None
|
1217
|
-
|
1218
|
-
# Popup cleanup
|
1219
|
-
if self.roi_manager_dialog is not None:
|
1220
|
-
self.roi_manager_dialog.reject()
|
1221
|
-
self.roi_manager_dialog = None
|
492
|
+
self.layer_removed.disconnect(self._on_layer_removed)
|
493
|
+
for layer_name in list(self.subscriptions.keys()):
|
494
|
+
config = self.subscriptions[layer_name]
|
495
|
+
if config.monitor is not None:
|
496
|
+
self.disconnect_monitor(config.monitor)
|
497
|
+
del self.subscriptions[layer_name]
|
498
|
+
self.subscriptions.clear()
|
1222
499
|
|
1223
500
|
# Toolbar cleanup
|
1224
501
|
self.toolbar.widgets["monitor"].widget.close()
|
1225
502
|
self.toolbar.widgets["monitor"].widget.deleteLater()
|
1226
|
-
|
1227
|
-
# ROI plots cleanup
|
1228
|
-
self.x_roi.cleanup_pyqtgraph()
|
1229
|
-
self.y_roi.cleanup_pyqtgraph()
|
1230
|
-
|
1231
503
|
super().cleanup()
|
1232
504
|
|
1233
505
|
|