Commit 2c64846a authored by Matias Guijarro's avatar Matias Guijarro
Browse files

Merge branch 'improve-roi-handling' into 'master'

Flint: Improve ROI display and edition

Closes #2667 and #2737

See merge request !3706
parents 700dc002 9fe3277a
Pipeline #46964 passed with stages
in 120 minutes and 8 seconds
...@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a content menu option to center profile ROIs in image/scatter - Added a content menu option to center profile ROIs in image/scatter
- Added curve stack as custom plot - Added curve stack as custom plot
- Added an overlay with size of Lima rect ROIs - Added an overlay with size of Lima rect ROIs
- Added tools to duplicate/rename ROIs during ROI edition
- Better handling of timeout, and try not to have 30s - Better handling of timeout, and try not to have 30s
- Better handling of stucked state - Better handling of stucked state
- Added `restart_flint` command from `bliss.common.plot` - Added `restart_flint` command from `bliss.common.plot`
...@@ -31,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -31,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `--enable-watchdog` command line argument to log and kill Flint if - Added `--enable-watchdog` command line argument to log and kill Flint if
too much memory is used too much memory is used
- `scan_info["requests"]` is not anymore read (replaced by `channels`) - `scan_info["requests"]` is not anymore read (replaced by `channels`)
- Update to silx 0.15 - Update to silx 0.15.1
- Demo - Demo
- Added regulation mock to the demo session - Added regulation mock to the demo session
- Scan publication - Scan publication
...@@ -58,6 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -58,6 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed slow rendering occurred on live curves and scatters with fast scans - Fixed slow rendering occurred on live curves and scatters with fast scans
- The video image is now also used for Lima EXTRERNAL_TRIGGER and EXTERNAL_GATE - The video image is now also used for Lima EXTRERNAL_TRIGGER and EXTERNAL_GATE
- Fixed blinking of the regulation plot legend - Fixed blinking of the regulation plot legend
- Fixed undisplayed ROIs during a scan. A tool is provided to display them
if not already selected.
### Removed ### Removed
......
...@@ -167,7 +167,6 @@ class ScanModelReader: ...@@ -167,7 +167,6 @@ class ScanModelReader:
self._acquisition_chain_description = scan_info.get("acquisition_chain", {}) self._acquisition_chain_description = scan_info.get("acquisition_chain", {})
self._device_description = scan_info.get("devices", {}) self._device_description = scan_info.get("devices", {})
self._channel_description = scan_info.get("channels", {}) self._channel_description = scan_info.get("channels", {})
self._rois_description = scan_info.get("rois", {})
scan_info = self._scan_info scan_info = self._scan_info
is_group = scan_info.get("is-scan-sequence", False) is_group = scan_info.get("is-scan-sequence", False)
...@@ -321,26 +320,32 @@ class ScanModelReader: ...@@ -321,26 +320,32 @@ class ScanModelReader:
pass pass
class LimaRoiDeviceParser(DefaultDeviceParser): class LimaRoiDeviceParser(DefaultDeviceParser):
def parse_channels(self, device, meta): def parse_channels(self, device: scan_model.Device, meta: Dict):
# cache virtual roi devices # cache virtual roi devices
virtual_rois = {} virtual_rois = {}
# FIXME: It would be good to have a real ROI concept in BLISS
# Here we iterate the set of metadata to try to find something interesting
for roi_name, roi_dict in meta.items():
if not isinstance(roi_dict, dict):
continue
if "kind" not in roi_dict:
continue
roi_device = self.create_virtual_roi(roi_name, roi_dict, device)
virtual_rois[roi_name] = roi_device
def get_virtual_roi(channel_fullname): def get_virtual_roi(channel_fullname):
"""Some magic to create virtual device for each ROIs""" """Retrieve roi device from channel name"""
nonlocal virtual_rois
short_name = channel_fullname.rsplit(":", 1)[-1] short_name = channel_fullname.rsplit(":", 1)[-1]
# FIXME: It would be good to have a real ROI concept in BLISS
if "_" in short_name: if "_" in short_name:
roi_name, _ = short_name.rsplit("_", 1) roi_name, _ = short_name.rsplit("_", 1)
else: else:
roi_name = short_name roi_name = short_name
key = f"{device.master().name()}:{device.name()}:{roi_name}" return virtual_rois.get(roi_name, None)
if key in virtual_rois:
return virtual_rois[key]
roi_device = self.create_virtual_roi(roi_name, key, device)
virtual_rois[key] = roi_device
return roi_device
channel_names = meta.get("channels", []) channel_names = meta.get("channels", [])
for channel_fullname in channel_names: for channel_fullname in channel_names:
...@@ -354,22 +359,32 @@ class ScanModelReader: ...@@ -354,22 +359,32 @@ class ScanModelReader:
) )
continue continue
roi_device = get_virtual_roi(channel_fullname) roi_device = get_virtual_roi(channel_fullname)
self.parse_channel(channel_fullname, channel_meta, parent=roi_device) if roi_device is not None:
parent_channel = roi_device
else:
parent_channel = device
self.parse_channel(
channel_fullname, channel_meta, parent=parent_channel
)
def create_virtual_roi(self, roi_name, key, parent): def create_virtual_roi(self, roi_name, roi_dict, parent):
device = scan_model.Device(self.reader._scan) device = scan_model.Device(self.reader._scan)
device.setName(roi_name) device.setName(roi_name)
device.setMaster(parent) device.setMaster(parent)
device.setType(scan_model.DeviceType.VIRTUAL_ROI) device.setType(scan_model.DeviceType.VIRTUAL_ROI)
# Read metadata # Read metadata
roi_dict = self.reader._rois_description.get(key)
roi = None roi = None
if roi_dict is not None: if roi_dict is not None:
try: try:
roi = lima_roi.dict_to_roi(roi_dict) roi = lima_roi.dict_to_roi(roi_dict)
except Exception: except Exception:
_logger.warning("Error while reading roi '%s'", key, exc_info=True) _logger.warning(
"Error while reading roi '%s' from '%s'",
roi_name,
device.fullName(),
exc_info=True,
)
metadata = scan_model.DeviceMetadata({}, roi) metadata = scan_model.DeviceMetadata({}, roi)
device.setMetadata(metadata) device.setMetadata(metadata)
...@@ -630,6 +645,9 @@ def create_plot_model( ...@@ -630,6 +645,9 @@ def create_plot_model(
Use the `plots` description or infer the plots from the `acquisition_chain`. Use the `plots` description or infer the plots from the `acquisition_chain`.
Finally update the selection using `_display_extra`. Finally update the selection using `_display_extra`.
""" """
if scan is None:
scan = create_scan_model(scan_info)
if "plots" in scan_info: if "plots" in scan_info:
plots = read_plot_models(scan_info) plots = read_plot_models(scan_info)
for plot in plots: for plot in plots:
...@@ -642,12 +660,12 @@ def create_plot_model( ...@@ -642,12 +660,12 @@ def create_plot_model(
return True return True
return False return False
aq_plots = infer_plot_models(scan_info) aq_plots = infer_plot_models(scan)
for plot in aq_plots: for plot in aq_plots:
if not contains_default_plot_kind(plots, plot): if not contains_default_plot_kind(plots, plot):
plots.append(plot) plots.append(plot)
else: else:
plots = infer_plot_models(scan_info) plots = infer_plot_models(scan)
def filter_with_scan_content(channel_names, scan): def filter_with_scan_content(channel_names, scan):
if scan is None: if scan is None:
...@@ -890,7 +908,35 @@ def _infer_default_scatter_plot(scan_info: Dict) -> List[plot_model.Plot]: ...@@ -890,7 +908,35 @@ def _infer_default_scatter_plot(scan_info: Dict) -> List[plot_model.Plot]:
return plots return plots
def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]: def _initialize_image_plot_from_device(device: scan_model.Device) -> plot_model.Plot:
"""Initialize ImagePlot with default information which can be used
structurally"""
plot = plot_item_model.ImagePlot()
# Reach a name which is stable between 2 scans
# FIXME: This have to be provided by the scan_info
def get_stable_name(device):
for channel in device.channels():
name = channel.name()
return name.rsplit(":", 1)[0]
return device.fullName().split(":", 1)[1]
stable_name = get_stable_name(device)
plot.setDeviceName(stable_name)
if device.type() == scan_model.DeviceType.LIMA:
for sub_device in device.devices():
if sub_device.name() in ["roi_counters", "roi_profiles"]:
for roi_device in sub_device.devices():
if roi_device.type() != scan_model.DeviceType.VIRTUAL_ROI:
continue
item = plot_item_model.RoiItem(plot)
item.setDeviceName(roi_device.fullName())
plot.addItem(item)
return plot
def infer_plot_models(scan: scan_model.Scan) -> List[plot_model.Plot]:
"""Infer description of plot models from a scan_info using """Infer description of plot models from a scan_info using
`acquisition_chain`. `acquisition_chain`.
...@@ -907,6 +953,7 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]: ...@@ -907,6 +953,7 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]:
result: List[plot_model.Plot] = [] result: List[plot_model.Plot] = []
default_plot = None default_plot = None
scan_info = scan.scanInfo()
acquisition_chain = scan_info.get("acquisition_chain", None) acquisition_chain = scan_info.get("acquisition_chain", None)
if len(acquisition_chain.keys()) == 1: if len(acquisition_chain.keys()) == 1:
...@@ -1043,45 +1090,23 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]: ...@@ -1043,45 +1090,23 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]:
# Image plot # Image plot
for master_name in acquisition_chain.keys(): for device in scan.devices():
for device_id in acquisition_chain[master_name].get("devices", []): plot = None
device_info = scan_info["devices"].get(device_id, {}) for channel in device.channels():
device_type = device_info.get("type") if channel.type() != scan_model.ChannelType.IMAGE:
device_name = device_id.rsplit(":", 1)[-1] continue
plot = None
for channel_name in device_info.get("channels", []):
channel_info = scan_info["channels"].get(channel_name, {})
dim = channel_info.get("dim", 0)
if dim != 2:
continue
if plot is None: if plot is None:
plot = plot_item_model.ImagePlot() plot = _initialize_image_plot_from_device(device)
device_name = get_device_from_channel(channel_name) if default_plot is None:
plot.setDeviceName(device_name) default_plot = plot
if default_plot is None:
default_plot = plot
if device_type == "lima":
if "rois" in scan_info:
for roi_name, _roi_dict in scan_info["rois"].items():
if not roi_name.startswith(
f"{device_name}:roi_counters:"
) and not roi_name.startswith(
f"{device_name}:roi_profiles:"
):
pass
item = plot_item_model.RoiItem(plot)
item.setDeviceName(f"{master_name}:{roi_name}")
plot.addItem(item)
image_channel = plot_model.ChannelRef(plot, channel_name)
item = plot_item_model.ImageItem(plot)
item.setImageChannel(image_channel)
plot.addItem(item)
if plot is not None: image_channel = plot_model.ChannelRef(plot, channel.name())
result.append(plot) item = plot_item_model.ImageItem(plot)
item.setImageChannel(image_channel)
plot.addItem(item)
if plot is not None:
result.append(plot)
# Move the default plot on top # Move the default plot on top
if default_plot is not None: if default_plot is not None:
......
...@@ -475,6 +475,12 @@ class Device(qt.QObject, _Sealable): ...@@ -475,6 +475,12 @@ class Device(qt.QObject, _Sealable):
def scan(self) -> Scan: def scan(self) -> Scan:
return self.parent() return self.parent()
def devices(self) -> List[Device]:
"""List sub devices from this device"""
for d in self.scan().devices():
if d.isChildOf(self):
yield d
def seal(self): def seal(self):
for channel in self.__channels: for channel in self.__channels:
channel.seal() channel.seal()
......
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg39" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata id="metadata43"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><path id="rect821-6" d="m9.3929 22.623h-6.7611v-19.375h16.37v1.9586" fill="none" stroke="#00a14b" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"/><rect id="rect821" x="12.323" y="8.1873" width="16.795" height="20.315" fill="none" stroke="#00a14b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.5"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg39" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata id="metadata43"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g id="g930" transform="rotate(-45 32.518 54.963)"><rect id="rect862" x="44.639" y="10.919" width="18.389" height="10.566" fill="none" stroke="#00a14b" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.6"/><rect id="rect882" x="61.554" y="10.919" width="1.4744" height="10.435" fill="#00a14b"/><path id="path895" d="m44.639 10.919-5.4729 5.283 5.4729 5.283z" fill="none" stroke="#00a14b" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.6"/><path id="path895-5" d="m41.608 14.582-1.6783 1.62 1.6783 1.62z" fill="#00a14b"/></g><path id="rect932" d="m28.416 16.388v10.542h-24.833v-16.875h6.9444" fill="none" stroke="#00a14b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.5"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg10" enable-background="new 0 0 32 32" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata id="metadata2"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g id="g8" transform="matrix(12.389 0 0 12.389 -774.02 -321.87)"><path id="path4" d="m64.059 26.477h0.52917v1.5875h-0.52917" fill="none" stroke="#00a14b" stroke-linecap="square" stroke-width=".20179"/><path id="path6" d="m63.397 26.422v0.45117h-0.5293v0.79492h0.5293v0.45117l0.84961-0.84766z" color="#000000" color-rendering="auto" dominant-baseline="auto" enable-background="accumulate" fill="#00a14b" image-rendering="auto" shape-rendering="auto" solid-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/></g></svg>
...@@ -32,14 +32,7 @@ class QTreeView(qt.QTreeView): ...@@ -32,14 +32,7 @@ class QTreeView(qt.QTreeView):
self.closePersistentEditor = self._closePersistentEditor self.closePersistentEditor = self._closePersistentEditor
def _uniqueId(self, index): def _uniqueId(self, index):
if not index.isValid(): return qt.QPersistentModelIndex(index)
return None
path = []
while index.isValid():
path.append(index.row())
path.append(index.column())
index = index.parent()
return tuple(path)
def _isPersistentEditorOpen(self, index): def _isPersistentEditorOpen(self, index):
unique = self._uniqueId(index) unique = self._uniqueId(index)
......
...@@ -581,21 +581,18 @@ class ImagePlotWidget(plot_helper.PlotWidget): ...@@ -581,21 +581,18 @@ class ImagePlotWidget(plot_helper.PlotWidget):
if self.__scan is None: if self.__scan is None:
return return
master = None limaDevice = None
for device in self.__scan.devices(): for device in self.__scan.devices():
if device.name() != self.deviceName(): if device.type() != scan_model.DeviceType.LIMA:
continue continue
if device.master() and device.master().isMaster(): if device.name() == self.deviceName():
master = device limaDevice = device
break break
if master is None: if limaDevice is None:
return return
for device in self.__scan.devices(): for device in limaDevice.devices():
if not device.isChildOf(master):
continue
roi = device.metadata().roi roi = device.metadata().roi
if roi is None: if roi is None:
continue continue
......
...@@ -90,6 +90,17 @@ class _DataItem(_property_tree_helper.ScanRowItem): ...@@ -90,6 +90,17 @@ class _DataItem(_property_tree_helper.ScanRowItem):
self.setDeviceLookAndFeel(device) self.setDeviceLookAndFeel(device)
self.__used.setCheckable(False) self.__used.setCheckable(False)
if device.type() == scan_model.DeviceType.VIRTUAL_ROI:
state = qt.Qt.Unchecked
self.__displayed.modelUpdated = None
self.__displayed.setData(state, role=delegates.VisibilityRole)
self.__displayed.modelUpdated = weakref.WeakMethod(
self.__visibilityViewChanged
)
if self.__treeView.isPersistentEditorOpen(self.__displayed.index()):
self.__treeView.closePersistentEditor(self.__displayed.index())
self.__treeView.openPersistentEditor(self.__displayed.index())
def device(self): def device(self):
return self.__device return self.__device
...@@ -118,17 +129,21 @@ class _DataItem(_property_tree_helper.ScanRowItem): ...@@ -118,17 +129,21 @@ class _DataItem(_property_tree_helper.ScanRowItem):
if plotItem is not None: if plotItem is not None:
isVisible = plotItem.isVisible() isVisible = plotItem.isVisible()
state = qt.Qt.Checked if isVisible else qt.Qt.Unchecked state = qt.Qt.Checked if isVisible else qt.Qt.Unchecked
self.__displayed.modelUpdated = None
self.__displayed.setData(state, role=delegates.VisibilityRole) self.__displayed.setData(state, role=delegates.VisibilityRole)
self.__displayed.modelUpdated = weakref.WeakMethod( self.__displayed.modelUpdated = weakref.WeakMethod(
self.__visibilityViewChanged self.__visibilityViewChanged
) )
else: else:
self.__displayed.setData(None, role=delegates.VisibilityRole)
self.__displayed.modelUpdated = None self.__displayed.modelUpdated = None
self.__displayed.setData(None, role=delegates.VisibilityRole)
if self.__channel is None: if self.__channel is None:
self.setPlotItemLookAndFeel(plotItem) self.setPlotItemLookAndFeel(plotItem)
if self.__treeView.isPersistentEditorOpen(self.__displayed.index()):
_logger.error("close")
self.__treeView.closePersistentEditor(self.__displayed.index())
self.__treeView.openPersistentEditor(self.__displayed.index()) self.__treeView.openPersistentEditor(self.__displayed.index())
if not isRoiItem: if not isRoiItem:
self.__treeView.openPersistentEditor(self.__remove.index()) self.__treeView.openPersistentEditor(self.__remove.index())
......
...@@ -10,8 +10,12 @@ Provide a RoiSelectionWidget ...@@ -10,8 +10,12 @@ Provide a RoiSelectionWidget
""" """
import typing import typing
import logging
import functools
import re
from silx.gui import qt from silx.gui import qt
from silx.gui import icons
from silx.gui.plot.tools.roi import RegionOfInterestManager from silx.gui.plot.tools.roi import RegionOfInterestManager
from silx.gui.plot.tools.roi import RegionOfInterestTableWidget from silx.gui.plot.tools.roi import RegionOfInterestTableWidget
from silx.gui.plot.items.roi import RectangleROI from silx.gui.plot.items.roi import RectangleROI
...@@ -19,6 +23,9 @@ from silx.gui.plot.items.roi import RegionOfInterest ...@@ -19,6 +23,9 @@ from silx.gui.plot.items.roi import RegionOfInterest
from silx.gui.plot.tools.roi import RoiModeSelectorAction from silx.gui.plot.tools.roi import RoiModeSelectorAction
_logger = logging.getLogger(__name__)
class _AutoHideToolBar(qt.QToolBar): class _AutoHideToolBar(qt.QToolBar):
"""A toolbar which hide itself if no actions are visible""" """A toolbar which hide itself if no actions are visible"""
...@@ -36,6 +43,24 @@ class _AutoHideToolBar(qt.QToolBar): ...@@ -36,6 +43,24 @@ class _AutoHideToolBar(qt.QToolBar):
self.setVisible(visible) self.setVisible(visible)
class _RegionOfInterestManagerWithContextMenu(RegionOfInterestManager):
sigRoiContextMenuRequested = qt.Signal(object, qt.QMenu)
def _feedContextMenu(self, menu):
RegionOfInterestManager._feedContextMenu(self, menu)
roi = self.getCurrentRoi()
if roi is not None:
if roi.isEditable():
self.sigRoiContextMenuRequested.emit(roi, menu)
def getRoiByName(self, name):
for r in self.getRois():
if r.getName() == name:
return r
return None
class RoiSelectionWidget(qt.QWidget): class RoiSelectionWidget(qt.QWidget):
selectionFinished = qt.Signal(object) selectionFinished = qt.Signal(object)
...@@ -48,21 +73,52 @@ class RoiSelectionWidget(qt.QWidget): ...@@ -48,21 +73,52 @@ class RoiSelectionWidget(qt.QWidget):
mode = plot.getInteractiveMode()["mode"] mode = plot.getInteractiveMode()["mode"]
self.__previousMode = mode self.__previousMode = mode
self.roiManager = RegionOfInterestManager(plot) self.roiManager = _RegionOfInterestManagerWithContextMenu(plot)
self.roiManager.setColor("pink") self.roiManager.setColor("pink")
self.roiManager.sigRoiAdded.connect(self.__roiAdded) self.roiManager.sigRoiAdded.connect(self.__roiAdded)
self.roiManager.sigRoiContextMenuRequested.connect(self.roiContextMenuRequested)
self.roiManager.sigCurrentRoiChanged.connect(self.__currentRoiChanged)
self.table = RegionOfInterestTableWidget() self.table = RegionOfInterestTableWidget()
self.table.setSelectionBehavior(qt.QAbstractItemView.SelectRows)
self.table.setSelectionMode(qt.QAbstractItemView.SingleSelection)
selectionModel = self.table.selectionModel()
selectionModel.currentRowChanged.connect(self.__currentRowChanged)
# Hide coords # Hide coords
horizontalHeader = self.table.horizontalHeader() horizontalHeader = self.table.horizontalHeader()
horizontalHeader.setSectionResizeMode(0, qt.QHeaderView.Stretch) horizontalHeader.setSectionResizeMode(0, qt.QHeaderView.Stretch)
horizontalHeader.hideSection(3) horizontalHeader.hideSection(1) # is editable
horizontalHeader.hideSection(3) # coords
self.table.setRegionOfInterestManager(self.roiManager) self.table.setRegionOfInterestManager(self.roiManager)
if kinds is None: if kinds is None:
kinds = [RectangleROI] kinds = [RectangleROI]
self.roiToolbar = qt.QToolBar(self) self.roiToolbar = qt.QToolBar(self)
cloneAction = qt.QAction(self.roiManager)
cloneAction.setText("Duplicate")
cloneAction.setToolTip("Duplicate selected ROI")
icon = icons.getQIcon("flint:icons/roi-duplicate")
cloneAction.setIcon(icon)
cloneAction.setEnabled(False)
cloneAction.triggered.connect(self.cloneCurrentRoiRequested)
self.__cloneAction = cloneAction
renameAction = qt.QAction(self.roiManager)
renameAction.setText("Rename")
renameAction.setToolTip("Rename selected ROI")
icon = icons.getQIcon("flint:icons/roi-rename")
renameAction.setIcon(icon)
renameAction.setEnabled(False)