bec-widgets 2.5.4__py3-none-any.whl → 2.7.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.
Files changed (29) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.yml +41 -0
  2. .gitlab/issue_templates/documentation_update_template.md → .github/ISSUE_TEMPLATE/documentation_update.md +10 -0
  3. .github/ISSUE_TEMPLATE/feature_request.md +3 -2
  4. .gitlab/merge_request_templates/default.md → .github/pull_request_template.md +8 -3
  5. .github/scripts/pr_issue_sync/pr_issue_sync.py +342 -0
  6. .github/scripts/pr_issue_sync/requirements.txt +2 -0
  7. .github/workflows/sync-issues-pr.yml +40 -0
  8. CHANGELOG.md +47 -0
  9. PKG-INFO +1 -1
  10. bec_widgets/cli/client.py +31 -1
  11. bec_widgets/examples/jupyter_console/jupyter_console_window.py +1 -1
  12. bec_widgets/utils/crosshair.py +35 -11
  13. bec_widgets/widgets/plots/image/image.py +219 -19
  14. bec_widgets/widgets/plots/image/image_roi_plot.py +37 -0
  15. bec_widgets/widgets/plots/image/setting_widgets/__init__.py +0 -0
  16. bec_widgets/widgets/plots/image/setting_widgets/image_roi_tree.py +375 -0
  17. bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +4 -3
  18. bec_widgets/widgets/plots/roi/image_roi.py +36 -14
  19. bec_widgets/widgets/plots/toolbar_bundles/mouse_interactions.py +1 -1
  20. bec_widgets/widgets/plots/waveform/waveform.py +2 -0
  21. {bec_widgets-2.5.4.dist-info → bec_widgets-2.7.0.dist-info}/METADATA +1 -1
  22. {bec_widgets-2.5.4.dist-info → bec_widgets-2.7.0.dist-info}/RECORD +26 -22
  23. pyproject.toml +1 -1
  24. .github/ISSUE_TEMPLATE/bug_report.md +0 -26
  25. .gitlab/issue_templates/bug_report_template.md +0 -17
  26. .gitlab/issue_templates/feature_request_template.md +0 -40
  27. {bec_widgets-2.5.4.dist-info → bec_widgets-2.7.0.dist-info}/WHEEL +0 -0
  28. {bec_widgets-2.5.4.dist-info → bec_widgets-2.7.0.dist-info}/entry_points.txt +0 -0
  29. {bec_widgets-2.5.4.dist-info → bec_widgets-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,375 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import TYPE_CHECKING
5
+
6
+ from bec_qthemes import material_icon
7
+ from qtpy.QtCore import QEvent, Qt
8
+ from qtpy.QtGui import QColor
9
+ from qtpy.QtWidgets import (
10
+ QColorDialog,
11
+ QHeaderView,
12
+ QSpinBox,
13
+ QToolButton,
14
+ QTreeWidget,
15
+ QTreeWidgetItem,
16
+ QVBoxLayout,
17
+ QWidget,
18
+ )
19
+
20
+ from bec_widgets import BECWidget
21
+ from bec_widgets.utils import BECDispatcher, ConnectionConfig
22
+ from bec_widgets.utils.toolbar import MaterialIconAction, ModularToolBar
23
+ from bec_widgets.widgets.plots.roi.image_roi import (
24
+ BaseROI,
25
+ CircularROI,
26
+ RectangularROI,
27
+ ROIController,
28
+ )
29
+ from bec_widgets.widgets.utility.visual.color_button_native.color_button_native import (
30
+ ColorButtonNative,
31
+ )
32
+ from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget
33
+
34
+ if TYPE_CHECKING:
35
+ from bec_widgets.widgets.plots.image.image import Image
36
+
37
+
38
+ class ROIPropertyTree(BECWidget, QWidget):
39
+ """
40
+ Two-column tree: [ROI] [Properties]
41
+
42
+ - Top-level: ROI name (editable) + color button.
43
+ - Children: type, line-width (spin box), coordinates (auto-updating).
44
+
45
+ Args:
46
+ image_widget (Image): The main Image widget that displays the ImageItem.
47
+ Provides ``plot_item`` and owns an ROIController already.
48
+ controller (ROIController, optional): Optionally pass an external controller.
49
+ If None, the manager uses ``image_widget.roi_controller``.
50
+ parent (QWidget, optional): Parent widget. Defaults to None.
51
+ """
52
+
53
+ PLUGIN = False
54
+ RPC = False
55
+
56
+ COL_ACTION, COL_ROI, COL_PROPS = range(3)
57
+ DELETE_BUTTON_COLOR = "#CC181E"
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ parent: QWidget = None,
63
+ image_widget: Image,
64
+ controller: ROIController | None = None,
65
+ ):
66
+
67
+ super().__init__(
68
+ parent=parent, config=ConnectionConfig(widget_class=self.__class__.__name__)
69
+ )
70
+
71
+ if controller is None:
72
+ # Use the controller already belonging to the Image widget
73
+ controller = getattr(image_widget, "roi_controller", None)
74
+ if controller is None:
75
+ controller = ROIController()
76
+ image_widget.roi_controller = controller
77
+
78
+ self.image_widget = image_widget
79
+ self.plot = image_widget.plot_item
80
+ self.controller = controller
81
+ self.roi_items: dict[BaseROI, QTreeWidgetItem] = {}
82
+
83
+ self.layout = QVBoxLayout(self)
84
+ self._init_toolbar()
85
+ self._init_tree()
86
+
87
+ # connect controller
88
+ self.controller.roiAdded.connect(self._on_roi_added)
89
+ self.controller.roiRemoved.connect(self._on_roi_removed)
90
+ self.controller.cleared.connect(self.tree.clear)
91
+
92
+ # initial load
93
+ for r in self.controller.rois:
94
+ self._on_roi_added(r)
95
+
96
+ self.tree.collapseAll()
97
+
98
+ # --------------------------------------------------------------------- UI
99
+ def _init_toolbar(self):
100
+ tb = ModularToolBar(self, self, orientation="horizontal")
101
+ # --- ROI draw actions (toggleable) ---
102
+ self.add_rect_action = MaterialIconAction("add_box", "Add Rect ROI", True, self)
103
+ self.add_circle_action = MaterialIconAction("add_circle", "Add Circle ROI", True, self)
104
+ tb.add_action("Add Rect ROI", self.add_rect_action, self)
105
+ tb.add_action("Add Circle ROI", self.add_circle_action, self)
106
+
107
+ # Expand/Collapse toggle
108
+ self.expand_toggle = MaterialIconAction(
109
+ "unfold_more", "Expand/Collapse", checkable=True, parent=self # icon when collapsed
110
+ )
111
+ tb.add_action("Expand/Collapse", self.expand_toggle, self)
112
+
113
+ def _exp_toggled(on: bool):
114
+ if on:
115
+ # switched to expanded state
116
+ self.tree.expandAll()
117
+ new_icon = material_icon("unfold_less", size=(20, 20), convert_to_pixmap=False)
118
+ else:
119
+ # collapsed state
120
+ self.tree.collapseAll()
121
+ new_icon = material_icon("unfold_more", size=(20, 20), convert_to_pixmap=False)
122
+ self.expand_toggle.action.setIcon(new_icon)
123
+
124
+ self.expand_toggle.action.toggled.connect(_exp_toggled)
125
+
126
+ self.expand_toggle.action.setChecked(False)
127
+ # colormap widget
128
+ self.cmap = BECColorMapWidget(cmap=self.controller.colormap)
129
+ tb.addWidget(QWidget()) # spacer
130
+ tb.addWidget(self.cmap)
131
+ self.cmap.colormap_changed_signal.connect(self.controller.set_colormap)
132
+ self.layout.addWidget(tb)
133
+ self.controller.paletteChanged.connect(lambda cmap: setattr(self.cmap, "colormap", cmap))
134
+
135
+ # ROI drawing state
136
+ self._roi_draw_mode = None # 'rect' | 'circle' | None
137
+ self._roi_start_pos = None # QPointF in image coords
138
+ self._temp_roi = None # live ROI being resized while dragging
139
+
140
+ # toggle handlers
141
+ self.add_rect_action.action.toggled.connect(
142
+ lambda on: self._set_roi_draw_mode("rect" if on else None)
143
+ )
144
+ self.add_circle_action.action.toggled.connect(
145
+ lambda on: self._set_roi_draw_mode("circle" if on else None)
146
+ )
147
+ # capture mouse events on the plot scene
148
+ self.plot.scene().installEventFilter(self)
149
+
150
+ def _init_tree(self):
151
+ self.tree = QTreeWidget()
152
+ self.tree.setColumnCount(3)
153
+ self.tree.setHeaderLabels(["Actions", "ROI", "Properties"])
154
+ self.tree.header().setSectionResizeMode(self.COL_ACTION, QHeaderView.ResizeToContents)
155
+ self.tree.headerItem().setText(self.COL_ACTION, "Actions") # blank header text
156
+ self.tree.itemChanged.connect(self._on_item_edited)
157
+ self.layout.addWidget(self.tree)
158
+
159
+ ################################################################################
160
+ # Helper functions
161
+ ################################################################################
162
+
163
+ # --------------------------------------------------------------------- formatting
164
+ @staticmethod
165
+ def _format_coord_text(value) -> str:
166
+ """
167
+ Consistently format a coordinate value for display.
168
+ """
169
+ if isinstance(value, (tuple, list)):
170
+ return "(" + ", ".join(f"{v:.2f}" for v in value) + ")"
171
+ if isinstance(value, (int, float)):
172
+ return f"{value:.2f}"
173
+ return str(value)
174
+
175
+ def _set_roi_draw_mode(self, mode: str | None):
176
+ # Ensure only the selected action is toggled on
177
+ if mode == "rect":
178
+ self.add_rect_action.action.setChecked(True)
179
+ self.add_circle_action.action.setChecked(False)
180
+ elif mode == "circle":
181
+ self.add_rect_action.action.setChecked(False)
182
+ self.add_circle_action.action.setChecked(True)
183
+ else:
184
+ self.add_rect_action.action.setChecked(False)
185
+ self.add_circle_action.action.setChecked(False)
186
+ self._roi_draw_mode = mode
187
+ self._roi_start_pos = None
188
+ # remove any unfinished temp ROI
189
+ if self._temp_roi is not None:
190
+ self.plot.removeItem(self._temp_roi)
191
+ self._temp_roi = None
192
+
193
+ def eventFilter(self, obj, event):
194
+ if self._roi_draw_mode is None:
195
+ return super().eventFilter(obj, event)
196
+ if event.type() == QEvent.GraphicsSceneMousePress and event.button() == Qt.LeftButton:
197
+ self._roi_start_pos = self.plot.vb.mapSceneToView(event.scenePos())
198
+ if self._roi_draw_mode == "rect":
199
+ self._temp_roi = RectangularROI(
200
+ pos=[self._roi_start_pos.x(), self._roi_start_pos.y()],
201
+ size=[5, 5],
202
+ parent_image=self.image_widget,
203
+ resize_handles=False,
204
+ )
205
+ if self._roi_draw_mode == "circle":
206
+ self._temp_roi = CircularROI(
207
+ pos=[self._roi_start_pos.x() - 2.5, self._roi_start_pos.y() - 2.5],
208
+ size=[5, 5],
209
+ parent_image=self.image_widget,
210
+ )
211
+ self.plot.addItem(self._temp_roi)
212
+ return True
213
+ elif event.type() == QEvent.GraphicsSceneMouseMove and self._temp_roi is not None:
214
+ pos = self.plot.vb.mapSceneToView(event.scenePos())
215
+ dx = pos.x() - self._roi_start_pos.x()
216
+ dy = pos.y() - self._roi_start_pos.y()
217
+
218
+ if self._roi_draw_mode == "rect":
219
+ self._temp_roi.setSize([dx, dy])
220
+ if self._roi_draw_mode == "circle":
221
+ r = max(
222
+ 1, math.hypot(dx, dy)
223
+ ) # radius never smaller than 1 for safety of handle mapping, otherwise SEGFAULT
224
+ d = 2 * r # diameter
225
+ self._temp_roi.setPos(self._roi_start_pos.x() - r, self._roi_start_pos.y() - r)
226
+ self._temp_roi.setSize([d, d])
227
+ return True
228
+ elif (
229
+ event.type() == QEvent.GraphicsSceneMouseRelease
230
+ and event.button() == Qt.LeftButton
231
+ and self._temp_roi is not None
232
+ ):
233
+ # finalize ROI
234
+ final_roi = self._temp_roi
235
+ self._temp_roi = None
236
+ self._set_roi_draw_mode(None)
237
+ # register via controller
238
+ final_roi.add_scale_handle()
239
+ self.controller.add_roi(final_roi)
240
+ return True
241
+ return super().eventFilter(obj, event)
242
+
243
+ # --------------------------------------------------------- controller slots
244
+ def _on_roi_added(self, roi: BaseROI):
245
+ # parent row with blank action column, name in ROI column
246
+ parent = QTreeWidgetItem(self.tree, ["", "", ""])
247
+ parent.setText(self.COL_ROI, roi.label)
248
+ parent.setFlags(parent.flags() | Qt.ItemIsEditable)
249
+ # --- delete button in actions column ---
250
+ del_btn = QToolButton()
251
+ delete_icon = material_icon(
252
+ "delete",
253
+ size=(20, 20),
254
+ convert_to_pixmap=False,
255
+ filled=False,
256
+ color=self.DELETE_BUTTON_COLOR,
257
+ )
258
+ del_btn.setIcon(delete_icon)
259
+ self.tree.setItemWidget(parent, self.COL_ACTION, del_btn)
260
+ del_btn.clicked.connect(lambda _=None, r=roi: self._delete_roi(r))
261
+ # color button
262
+ color_btn = ColorButtonNative(parent=self, color=roi.line_color)
263
+ self.tree.setItemWidget(parent, self.COL_PROPS, color_btn)
264
+ color_btn.clicked.connect(lambda: self._pick_color(roi, color_btn))
265
+
266
+ # child rows (3 columns: action, ROI, properties)
267
+ QTreeWidgetItem(parent, ["", "Type", roi.__class__.__name__])
268
+ width_item = QTreeWidgetItem(parent, ["", "Line width", ""])
269
+ width_spin = QSpinBox()
270
+ width_spin.setRange(1, 50)
271
+ width_spin.setValue(roi.line_width)
272
+ self.tree.setItemWidget(width_item, self.COL_PROPS, width_spin)
273
+ width_spin.valueChanged.connect(lambda v, r=roi: setattr(r, "line_width", v))
274
+
275
+ # --- Step 2: Insert separate coordinate rows (one per value)
276
+ coord_rows = {}
277
+ coords = roi.get_coordinates(typed=True)
278
+
279
+ for key, value in coords.items():
280
+ # Human-readable label: “center x” from “center_x”, etc.
281
+ label = key.replace("_", " ").title()
282
+ val_text = self._format_coord_text(value)
283
+ row = QTreeWidgetItem(parent, ["", label, val_text])
284
+ coord_rows[key] = row
285
+
286
+ # keep dict refs
287
+ self.roi_items[roi] = parent
288
+
289
+ # --- Step 3: Update coordinates on ROI movement
290
+ def _update_coords():
291
+ c_dict = roi.get_coordinates(typed=True)
292
+ for k, row in coord_rows.items():
293
+ if k in c_dict:
294
+ val = c_dict[k]
295
+ row.setText(self.COL_PROPS, self._format_coord_text(val))
296
+
297
+ if isinstance(roi, RectangularROI):
298
+ roi.edgesChanged.connect(_update_coords)
299
+ else:
300
+ roi.centerChanged.connect(_update_coords)
301
+
302
+ # sync width edits back to spinbox
303
+ roi.penChanged.connect(lambda r=roi, sp=width_spin: sp.setValue(r.line_width))
304
+ roi.nameChanged.connect(lambda n, itm=parent: itm.setText(self.COL_ROI, n))
305
+
306
+ # color changes
307
+ roi.penChanged.connect(lambda r=roi, b=color_btn: b.set_color(r.line_color))
308
+
309
+ for c in range(3):
310
+ self.tree.resizeColumnToContents(c)
311
+
312
+ def _on_roi_removed(self, roi: BaseROI):
313
+ item = self.roi_items.pop(roi, None)
314
+ if item:
315
+ idx = self.tree.indexOfTopLevelItem(item)
316
+ self.tree.takeTopLevelItem(idx)
317
+
318
+ # ---------------------------------------------------------- event handlers
319
+ def _pick_color(self, roi: BaseROI, btn: "ColorButtonNative"):
320
+ clr = QColorDialog.getColor(QColor(roi.line_color), self, "Select ROI Color")
321
+ if clr.isValid():
322
+ roi.line_color = clr.name()
323
+ btn.set_color(clr)
324
+
325
+ def _on_item_edited(self, item: QTreeWidgetItem, col: int):
326
+ if col != self.COL_ROI:
327
+ return
328
+ # find which roi
329
+ for r, it in self.roi_items.items():
330
+ if it is item:
331
+ r.label = item.text(self.COL_ROI)
332
+ break
333
+
334
+ def _delete_roi(self, roi):
335
+ self.controller.remove_roi(roi)
336
+
337
+ def cleanup(self):
338
+ self.cmap.close()
339
+ self.cmap.deleteLater()
340
+ super().cleanup()
341
+
342
+
343
+ # Demo
344
+ if __name__ == "__main__": # pragma: no cover
345
+ import sys
346
+
347
+ import numpy as np
348
+ from qtpy.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout
349
+
350
+ from bec_widgets.widgets.plots.image.image import Image
351
+
352
+ app = QApplication(sys.argv)
353
+
354
+ bec_dispatcher = BECDispatcher(gui_id="roi_tree_demo")
355
+ client = bec_dispatcher.client
356
+ client.start()
357
+
358
+ image_widget = Image(popups=False)
359
+ image_widget.main_image.set_data(np.random.normal(size=(200, 200)))
360
+
361
+ win = QWidget()
362
+ win.setWindowTitle("Modular ROI Demo")
363
+ ml = QHBoxLayout(win)
364
+
365
+ # Add the image widget on the left
366
+ ml.addWidget(image_widget)
367
+
368
+ # ROI manager linked to that image
369
+ mgr = ROIPropertyTree(parent=image_widget, image_widget=image_widget)
370
+ mgr.setFixedWidth(350)
371
+ ml.addWidget(mgr)
372
+
373
+ win.resize(1500, 600)
374
+ win.show()
375
+ sys.exit(app.exec_())
@@ -35,19 +35,20 @@ class MonitorSelectionToolbarBundle(ToolbarBundle):
35
35
  self.device_combo_box.addItem("", None)
36
36
  self.device_combo_box.setCurrentText("")
37
37
  self.device_combo_box.setToolTip("Select Device")
38
+ self.device_combo_box.setFixedWidth(150)
38
39
  self.device_combo_box.setItemDelegate(NoCheckDelegate(self.device_combo_box))
39
40
 
40
- self.add_action("monitor", WidgetAction(widget=self.device_combo_box, adjust_size=True))
41
+ self.add_action("monitor", WidgetAction(widget=self.device_combo_box, adjust_size=False))
41
42
 
42
43
  # 2) Dimension combo box
43
44
  self.dim_combo_box = QComboBox(parent=self.target_widget)
44
45
  self.dim_combo_box.addItems(["auto", "1d", "2d"])
45
46
  self.dim_combo_box.setCurrentText("auto")
46
47
  self.dim_combo_box.setToolTip("Monitor Dimension")
47
- self.dim_combo_box.setFixedWidth(60)
48
+ self.dim_combo_box.setFixedWidth(100)
48
49
  self.dim_combo_box.setItemDelegate(NoCheckDelegate(self.dim_combo_box))
49
50
 
50
- self.add_action("dim_combo", WidgetAction(widget=self.dim_combo_box, adjust_size=True))
51
+ self.add_action("dim_combo", WidgetAction(widget=self.dim_combo_box, adjust_size=False))
51
52
 
52
53
  # Connect slots, a device will be connected upon change of any combobox
53
54
  self.device_combo_box.currentTextChanged.connect(lambda: self.connect_monitor())
@@ -113,6 +113,7 @@ class BaseROI(BECConnector):
113
113
  "line_width.setter",
114
114
  "get_coordinates",
115
115
  "get_data_from_image",
116
+ "set_position",
116
117
  ]
117
118
 
118
119
  def __init__(
@@ -125,7 +126,7 @@ class BaseROI(BECConnector):
125
126
  # ROI-specific
126
127
  label: str | None = None,
127
128
  line_color: str | None = None,
128
- line_width: int = 10,
129
+ line_width: int = 5,
129
130
  # all remaining pg.*ROI kwargs (pos, size, pen, …)
130
131
  **pg_kwargs,
131
132
  ):
@@ -333,7 +334,22 @@ class BaseROI(BECConnector):
333
334
  def add_scale_handle(self):
334
335
  return
335
336
 
337
+ def set_position(self, x: float, y: float):
338
+ """
339
+ Sets the position of the ROI.
340
+
341
+ Args:
342
+ x (float): The x-coordinate of the new position.
343
+ y (float): The y-coordinate of the new position.
344
+ """
345
+ self.setPos(x, y)
346
+
336
347
  def remove(self):
348
+ # Delegate to controller first so that GUI managers stay in sync
349
+ controller = getattr(self.parent_image, "roi_controller", None)
350
+ if controller and self in controller.rois:
351
+ controller.remove_roi(self)
352
+ return # controller will call back into this method once deregistered
337
353
  handles = self.handles
338
354
  for i in range(len(handles)):
339
355
  try:
@@ -342,9 +358,8 @@ class BaseROI(BECConnector):
342
358
  continue
343
359
  self.rpc_register.remove_rpc(self)
344
360
  self.parent_image.plot_item.removeItem(self)
345
- if hasattr(self.parent_image, "roi_controller"):
346
- self.parent_image.roi_controller._rois.remove(self)
347
- self.parent_image.roi_controller._rebuild_color_buffer()
361
+ viewBox = self.parent_plot_item.vb
362
+ viewBox.update()
348
363
 
349
364
 
350
365
  class RectangularROI(BaseROI, pg.RectROI):
@@ -378,7 +393,7 @@ class RectangularROI(BaseROI, pg.RectROI):
378
393
  # ROI specifics
379
394
  label: str | None = None,
380
395
  line_color: str | None = None,
381
- line_width: int = 10,
396
+ line_width: int = 5,
382
397
  resize_handles: bool = True,
383
398
  **extra_pg,
384
399
  ):
@@ -414,8 +429,6 @@ class RectangularROI(BaseROI, pg.RectROI):
414
429
 
415
430
  self.sigRegionChanged.connect(self._on_region_changed)
416
431
  self.adorner = LabelAdorner(roi=self)
417
- if resize_handles:
418
- self.add_scale_handle()
419
432
  self.hoverPen = fn.mkPen(color=(255, 0, 0), width=3, style=QtCore.Qt.DashLine)
420
433
  self.handleHoverPen = fn.mkPen("lime", width=4)
421
434
 
@@ -440,6 +453,11 @@ class RectangularROI(BaseROI, pg.RectROI):
440
453
  self.addScaleHandle([0, 0.5], [1, 0.5]) # left edge
441
454
  self.addScaleHandle([1, 0.5], [0, 0.5]) # right edge
442
455
 
456
+ self.handlePen = fn.mkPen("#ffff00", width=5) # bright yellow outline
457
+ self.handleHoverPen = fn.mkPen("#00ffff", width=4) # cyan, thicker when hovered
458
+ self.handleBrush = (200, 200, 0, 120) # semi-transparent fill
459
+ self.handleHoverBrush = (0, 255, 255, 160)
460
+
443
461
  def _on_region_changed(self):
444
462
  """
445
463
  Handles ROI region change events.
@@ -544,7 +562,7 @@ class CircularROI(BaseROI, pg.CircleROI):
544
562
  parent_image: Image | None = None,
545
563
  label: str | None = None,
546
564
  line_color: str | None = None,
547
- line_width: int = 10,
565
+ line_width: int = 5,
548
566
  **extra_pg,
549
567
  ):
550
568
  """
@@ -725,7 +743,7 @@ class ROIController(QObject):
725
743
  roi.line_color = color
726
744
  # ensure line width default is at least 3 if not previously set
727
745
  if getattr(roi, "line_width", 0) < 1:
728
- roi.line_width = 10
746
+ roi.line_width = 5
729
747
  self.roiAdded.emit(roi)
730
748
 
731
749
  def remove_roi(self, roi: BaseROI):
@@ -738,8 +756,12 @@ class ROIController(QObject):
738
756
  Args:
739
757
  roi (BaseROI): The ROI instance to remove.
740
758
  """
741
- rois = self._rois
742
- if roi not in rois:
759
+ if roi in self._rois:
760
+ self.roiRemoved.emit(roi)
761
+ self._rois.remove(roi)
762
+ roi.remove()
763
+ self._rebuild_color_buffer()
764
+ else:
743
765
  roi.remove()
744
766
 
745
767
  def get_roi(self, index: int) -> BaseROI | None:
@@ -782,7 +804,7 @@ class ROIController(QObject):
782
804
  """
783
805
  roi = self.get_roi(index)
784
806
  if roi is not None:
785
- roi.remove()
807
+ self.remove_roi(roi)
786
808
 
787
809
  def remove_roi_by_name(self, name: str):
788
810
  """
@@ -793,7 +815,7 @@ class ROIController(QObject):
793
815
  """
794
816
  roi = self.get_roi_by_name(name)
795
817
  if roi is not None:
796
- roi.remove()
818
+ self.remove_roi(roi)
797
819
 
798
820
  def clear(self):
799
821
  """
@@ -803,7 +825,7 @@ class ROIController(QObject):
803
825
  the cleared signal to notify listeners that all ROIs have been removed.
804
826
  """
805
827
  for roi in list(self._rois):
806
- roi.remove()
828
+ self.remove_roi(roi)
807
829
  self.cleared.emit()
808
830
 
809
831
  def renormalize_colors(self):
@@ -44,7 +44,7 @@ class MouseInteractionToolbarBundle(ToolbarBundle):
44
44
  initial_action="drag_mode",
45
45
  tooltip="Mouse Modes",
46
46
  checkable=True,
47
- parent=self,
47
+ parent=self.target_widget,
48
48
  )
49
49
 
50
50
  # Add them to the bundle
@@ -414,6 +414,8 @@ class Waveform(PlotBase):
414
414
  """
415
415
  Slot for when the axis settings dialog is closed.
416
416
  """
417
+ self.dap_summary.close()
418
+ self.dap_summary.deleteLater()
417
419
  self.dap_summary_dialog.deleteLater()
418
420
  self.dap_summary_dialog = None
419
421
  self.toolbar.widgets["fit_params"].action.setChecked(False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.5.4
3
+ Version: 2.7.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets