From 18c027d91f8c959bff4bff5746ad161ff1df3059 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 7 Dec 2020 17:15:37 +0100 Subject: [PATCH 01/13] Add metadata per devices --- bliss/flint/model/scan_model.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/bliss/flint/model/scan_model.py b/bliss/flint/model/scan_model.py index 4cb025fc46..29409e5c1e 100755 --- a/bliss/flint/model/scan_model.py +++ b/bliss/flint/model/scan_model.py @@ -395,6 +395,11 @@ class DeviceType(enum.Enum): """ +class DeviceMetadata(NamedTuple): + roi: Optional[object] + """Define a ROI geometry, is one""" + + class Device(qt.QObject, _Sealable): """ Description of a device. @@ -403,10 +408,13 @@ class Device(qt.QObject, _Sealable): and channels. This could not exactly match the Bliss API. """ + _noneMetadata = DeviceMetadata(None) + def __init__(self, parent: Scan): qt.QObject.__init__(self, parent=parent) _Sealable.__init__(self) self.__name: str = "" + self.__metadata: DeviceMetadata = self._noneMetadata self.__type: DeviceType = DeviceType.NONE self.__channels: List[Channel] = [] self.__master: Optional[Device] = None @@ -429,6 +437,17 @@ class Device(qt.QObject, _Sealable): def name(self) -> str: return self.__name + def setMetadata(self, metadata: DeviceMetadata): + if self.isSealed(): + raise SealedError() + self.__metadata = metadata + + def metadata(self) -> DeviceMetadata: + """ + Returns a bunch of metadata stored within the channel. + """ + return self.__metadata + def addChannel(self, channel: Channel): if self.isSealed(): raise SealedError() @@ -645,7 +664,7 @@ class Channel(qt.QObject, _Sealable): def metadata(self) -> ChannelMetadata: """ - Returns a bunch of metadata stored withing the channel. + Returns a bunch of metadata stored within the channel. """ return self.__metadata -- GitLab From 51f725e4b9719aa91edd7e7574f9750e1a7089d6 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 7 Dec 2020 18:07:28 +0100 Subject: [PATCH 02/13] There should not be collision with ROI names --- bliss/flint/helper/scan_info_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bliss/flint/helper/scan_info_helper.py b/bliss/flint/helper/scan_info_helper.py index 2faf5ad9e6..3eebeaccdd 100755 --- a/bliss/flint/helper/scan_info_helper.py +++ b/bliss/flint/helper/scan_info_helper.py @@ -200,7 +200,7 @@ def create_scan_model(scan_info: Dict, is_group: bool = False) -> scan_model.Sca # guess the computation part do not contain _ # FIXME: It would be good to have a real ROI concept in BLISS roi_name, _ = short_name.rsplit("_", 1) - key = f"{channel_info.master}:{channel_info.device}:@@{roi_name}" + key = f"{channel_info.device}:{roi_name}" device = devices.get(key, None) if device is None: device = scan_model.Device(scan) -- GitLab From 8459093a01613d799fdc315d3f2964c1716e44dd Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 7 Dec 2020 18:08:05 +0100 Subject: [PATCH 03/13] Read ROI definition as metadata --- bliss/flint/helper/scan_info_helper.py | 43 +++++++++++++++------ tests/flint/helper/test_scan_info_helper.py | 41 +++++++++++++++++--- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/bliss/flint/helper/scan_info_helper.py b/bliss/flint/helper/scan_info_helper.py index 3eebeaccdd..18e095bb8d 100755 --- a/bliss/flint/helper/scan_info_helper.py +++ b/bliss/flint/helper/scan_info_helper.py @@ -22,6 +22,7 @@ from ..model import scan_model from ..model import plot_model from ..model import plot_item_model from . import model_helper +from bliss.controllers.lima import roi as lima_roi _logger = logging.getLogger(__name__) @@ -175,6 +176,25 @@ def create_scan_model(scan_info: Dict, is_group: bool = False) -> scan_model.Sca devices[key] = device return device + def create_virtual_roi(roi_name, key, parent): + device = scan_model.Device(scan) + device.setName(roi_name) + device.setMaster(parent) + device.setType(scan_model.DeviceType.VIRTUAL_ROI) + + # Read metadata + roi_dict = scan_info.get("rois", {}).get(key) + roi = None + if roi_dict is not None: + try: + roi = lima_roi.dict_to_roi(roi_dict) + except: + _logger.warning("Error while reading roi '%s'", key, exc_info=True) + + metadata = scan_model.DeviceMetadata(roi) + device.setMetadata(metadata) + return device + channelsDict = {} channels = iter_channels(scan_info) for channel_info in channels: @@ -195,20 +215,19 @@ def create_scan_model(scan_info: Dict, is_group: bool = False) -> scan_model.Sca short_name = name.rsplit(":")[-1] # Some magic to create virtual device for each ROIs - if parent.name() == "roi_counters": + if parent.name() in ["roi_counters", "roi_profiles"]: + # guess the computation part do not contain _ + # FIXME: It would be good to have a real ROI concept in BLISS if "_" in short_name: - # guess the computation part do not contain _ - # FIXME: It would be good to have a real ROI concept in BLISS roi_name, _ = short_name.rsplit("_", 1) - key = f"{channel_info.device}:{roi_name}" - device = devices.get(key, None) - if device is None: - device = scan_model.Device(scan) - device.setName(roi_name) - device.setMaster(parent) - device.setType(scan_model.DeviceType.VIRTUAL_ROI) - devices[key] = device - parent = device + else: + roi_name = short_name + key = f"{channel_info.device}:{roi_name}" + device = devices.get(key, None) + if device is None: + device = create_virtual_roi(roi_name, key, parent) + devices[key] = device + parent = device channel = scan_model.Channel(parent) channelsDict[channel_info.name] = channel diff --git a/tests/flint/helper/test_scan_info_helper.py b/tests/flint/helper/test_scan_info_helper.py index 616bb604fc..a51740133d 100755 --- a/tests/flint/helper/test_scan_info_helper.py +++ b/tests/flint/helper/test_scan_info_helper.py @@ -43,9 +43,28 @@ SCAN_INFO_LIMA_ROIS = { "beamviewer:roi_counters:roi1_avg", "beamviewer:roi_counters:roi4_sum", "beamviewer:roi_counters:roi4_avg", + "beamviewer:roi_counters:roi5_avg", ], } - } + }, + "rois": { + "beamviewer:roi_counters:roi1": { + "kind": "rect", + "x": 190, + "y": 110, + "width": 600, + "height": 230, + }, + "beamviewer:roi_counters:roi4": { + "kind": "arc", + "cx": 487.0, + "cy": 513.0, + "r1": 137.0, + "r2": 198.0, + "a1": -172.0, + "a2": -300.0, + }, + }, } @@ -100,13 +119,25 @@ def test_create_scan_model_with_lima_rois(): deviceCount = len(list(scan.devices())) for device in scan.devices(): channelCount += len(list(device.channels())) - assert channelCount == 6 - assert deviceCount == 6 + assert channelCount == 7 + assert deviceCount == 7 + + channel = scan.getChannelByName("beamviewer:roi_counters:roi1_avg") + device = channel.device() + assert device.metadata().roi is not None + assert device.metadata().roi.x == 190 channel = scan.getChannelByName("beamviewer:roi_counters:roi4_avg") assert channel.name() == "beamviewer:roi_counters:roi4_avg" - assert channel.device().name() == "roi4" - assert channel.device().type() == scan_model.DeviceType.VIRTUAL_ROI + device = channel.device() + assert device.name() == "roi4" + assert device.type() == scan_model.DeviceType.VIRTUAL_ROI + assert device.metadata().roi is not None + assert device.metadata().roi.cx == 487.0 + + channel = scan.getChannelByName("beamviewer:roi_counters:roi5_avg") + device = channel.device() + assert device.metadata().roi is None def test_create_plot_model(): -- GitLab From 2721256c1bd79ceb89471409b8207cdf9990b236 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 7 Dec 2020 19:27:34 +0100 Subject: [PATCH 04/13] Add isChildOf for devices --- bliss/flint/model/scan_model.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bliss/flint/model/scan_model.py b/bliss/flint/model/scan_model.py index 29409e5c1e..6c10cd6670 100755 --- a/bliss/flint/model/scan_model.py +++ b/bliss/flint/model/scan_model.py @@ -485,6 +485,15 @@ class Device(qt.QObject, _Sealable): # FIXME: This have to be improved return self.__master is None + def isChildOf(self, master: Device) -> bool: + """Returns true if this device is the child of `master` device.""" + parent = self.__master + while parent is not None: + if parent is master: + return True + parent = parent.__master + return False + def setType(self, deviceType: DeviceType): if self.isSealed(): raise SealedError() -- GitLab From 8f68a46033bbccb61fd601b642ba3493012b08ea Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 7 Dec 2020 19:35:08 +0100 Subject: [PATCH 05/13] Display ROIs into the image --- bliss/flint/widgets/image_plot.py | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/bliss/flint/widgets/image_plot.py b/bliss/flint/widgets/image_plot.py index d323c16182..b3755cae1d 100755 --- a/bliss/flint/widgets/image_plot.py +++ b/bliss/flint/widgets/image_plot.py @@ -19,6 +19,11 @@ from silx.gui import icons from silx.gui import colors from silx.gui.plot.actions import histogram from silx.gui.plot.items.marker import Marker +from silx.gui.plot.tools.roi import RegionOfInterestManager +from silx.gui.plot.items import roi as silx_rois +from bliss.controllers.lima import roi as lima_rois +from bliss.flint.widgets.utils import rois as flint_rois + from bliss.flint.model import scan_model from bliss.flint.model import flint_model @@ -139,6 +144,8 @@ class ImagePlotWidget(plot_helper.PlotWidget): self.__plot.setDataMargins(0.05, 0.05, 0.05, 0.05) self.__plot.getYAxis().setInverted(True) + self.__roiManager = RegionOfInterestManager(self.__plot) + self.__title = _Title(self.__plot) self.__colormap = colors.Colormap("viridis") @@ -538,6 +545,7 @@ class ImagePlotWidget(plot_helper.PlotWidget): def __scanStarted(self): self.__imageReceived = 0 + self.__updateRois() self.__refreshManager.scanStarted() self.__view.scanStarted() self.__title.scanStarted(self.__scan) @@ -548,6 +556,65 @@ class ImagePlotWidget(plot_helper.PlotWidget): self.__cleanAll() self.__title.scanFinished(self.__scan) + def __updateRois(self): + self.__roiManager.clear() + if self.__scan is None: + return + + master = None + for device in self.__scan.devices(): + if device.name() != self.deviceName(): + continue + if device.master() and device.master().isMaster(): + master = device + break + + if master is None: + return + + for device in self.__scan.devices(): + if not device.isChildOf(master): + continue + + roi = device.metadata().roi + if roi is None: + continue + + if isinstance(roi, lima_rois.RoiProfile): + # Must be checked first as a RoiProfile is a RectRoi + if roi.mode == "vertical": + item = flint_rois.LimaVProfileRoi() + elif roi.mode == "horizontal": + item = flint_rois.LimaHProfileRoi() + else: + item = silx_rois.RectangleROI() + origin = roi.x, roi.y + size = roi.width, roi.height + item.setGeometry(origin=origin, size=size) + elif isinstance(roi, lima_rois.Roi): + item = silx_rois.RectangleROI() + origin = roi.x, roi.y + size = roi.width, roi.height + item.setGeometry(origin=origin, size=size) + elif isinstance(roi, lima_rois.ArcRoi): + item = silx_rois.ArcROI() + center = roi.cx, roi.cy + item.setGeometry( + center=center, + innerRadius=roi.r1, + outerRadius=roi.r2, + startAngle=numpy.deg2rad(roi.a1), + endAngle=numpy.deg2rad(roi.a2), + ) + else: + item = None + if item is not None: + item.setName(device.name()) + item.setEditable(False) + item.setSelectable(False) + self.__roiManager.addRoi(item) + item.setColor(qt.QColor(0xA0, 0xA0, 0xA0)) + def __scanDataUpdated(self, event: scan_model.ScanDataUpdateEvent): plotModel = self.__plotModel if plotModel is None: -- GitLab From 72b92ba58f49bdd7c8ef97fdb9cd91176f40ff8f Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 15:04:23 +0100 Subject: [PATCH 06/13] Use name hierarchy in getDeviceByName --- bliss/flint/model/scan_model.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bliss/flint/model/scan_model.py b/bliss/flint/model/scan_model.py index 6c10cd6670..860098ed09 100755 --- a/bliss/flint/model/scan_model.py +++ b/bliss/flint/model/scan_model.py @@ -251,9 +251,18 @@ class Scan(qt.QObject, _Sealable): self.__devices.append(device) def getDeviceByName(self, name: str) -> Device: + elements = name.split(":") for device in self.__devices: - if device.name() == name: - return device + current = device + for e in reversed(elements): + if current is None or current.name() != e: + break + current = current.master() + else: + # The item was found + if current is None: + return device + raise ValueError("Device %s not found." % name) def _fireScanDataUpdated( -- GitLab From 2682fde2c1e8a9408babd2f007a3e76da3454761 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 15:07:47 +0100 Subject: [PATCH 07/13] Use RoiItem to display them in the image plot --- bliss/flint/helper/scan_info_helper.py | 12 ++++++- bliss/flint/model/plot_item_model.py | 37 +++++++++++++++++++++ bliss/flint/widgets/image_plot.py | 20 ++++++++--- tests/flint/helper/test_scan_info_helper.py | 15 ++++++++- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/bliss/flint/helper/scan_info_helper.py b/bliss/flint/helper/scan_info_helper.py index 18e095bb8d..039d1411dd 100755 --- a/bliss/flint/helper/scan_info_helper.py +++ b/bliss/flint/helper/scan_info_helper.py @@ -803,7 +803,7 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]: image_plots_per_device: Dict[str, List[plot_model.Plot]] = {} - for _master, channels in acquisition_chain.items(): + for master, channels in acquisition_chain.items(): images: List[str] = [] images += channels.get("images", []) # merge master which are image @@ -827,6 +827,16 @@ def infer_plot_models(scan_info: Dict) -> List[plot_model.Plot]: item.setImageChannel(image_channel) plot.addItem(item) + 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}:{roi_name}") + plot.addItem(item) + result.extend(image_plots_per_device.values()) # Final process diff --git a/bliss/flint/model/plot_item_model.py b/bliss/flint/model/plot_item_model.py index 1a30aa04b0..fae19e4e49 100755 --- a/bliss/flint/model/plot_item_model.py +++ b/bliss/flint/model/plot_item_model.py @@ -339,6 +339,43 @@ class ImageItem(plot_model.Item): self.__colormap = colormap +class RoiItem(plot_model.Item): + """Define a ROI as part of a plot. + """ + + def __init__(self, parent: plot_model.Plot = None): + super(RoiItem, self).__init__(parent=parent) + self.__deviceName: Optional[str] = None + + def __reduce__(self): + return (self.__class__, (), self.__getstate__()) + + def __getstate__(self): + state = super(RoiItem, self).__getstate__() + assert "deviceName" not in state + state["deviceName"] = self.__deviceName + return state + + def __setstate__(self, state): + super(RoiItem, self).__setstate__(state) + self.__deviceName = state.pop("deviceName") + + def roiName(self) -> str: + """Returns the name of the ROI""" + return self.__deviceName.split(":")[-1] + + def setDeviceName(self, name: str): + """Set the device name containing the ROI. + + The device name is the full device name prefixed by the top master name + """ + self.__deviceName = name + + def roi(self, scan): + device = scan.getDeviceByName(self.__deviceName) + return device.metadata().roi + + class ScatterPlot(plot_model.Plot): """Define a plot which displays scatters.""" diff --git a/bliss/flint/widgets/image_plot.py b/bliss/flint/widgets/image_plot.py index b3755cae1d..da7e94115b 100755 --- a/bliss/flint/widgets/image_plot.py +++ b/bliss/flint/widgets/image_plot.py @@ -545,7 +545,7 @@ class ImagePlotWidget(plot_helper.PlotWidget): def __scanStarted(self): self.__imageReceived = 0 - self.__updateRois() + self.__createScanRois() self.__refreshManager.scanStarted() self.__view.scanStarted() self.__title.scanStarted(self.__scan) @@ -556,7 +556,7 @@ class ImagePlotWidget(plot_helper.PlotWidget): self.__cleanAll() self.__title.scanFinished(self.__scan) - def __updateRois(self): + def __createScanRois(self): self.__roiManager.clear() if self.__scan is None: return @@ -614,6 +614,7 @@ class ImagePlotWidget(plot_helper.PlotWidget): item.setSelectable(False) self.__roiManager.addRoi(item) item.setColor(qt.QColor(0xA0, 0xA0, 0xA0)) + item.setVisible(False) def __scanDataUpdated(self, event: scan_model.ScanDataUpdateEvent): plotModel = self.__plotModel @@ -625,6 +626,8 @@ class ImagePlotWidget(plot_helper.PlotWidget): channelName = item.imageChannel().name() if event.isUpdatedChannelName(channelName): self.__updateItem(item) + elif isinstance(item, plot_item_model.RoiItem): + self.__updateItem(item) def __cleanAll(self): for _item, itemKeys in self.__items.items(): @@ -672,9 +675,16 @@ class ImagePlotWidget(plot_helper.PlotWidget): return if not item.isValid(): return - if not isinstance(item, plot_item_model.ImageItem): - return - + if isinstance(item, plot_item_model.ImageItem): + self.__updateImageItem(item) + elif isinstance(item, plot_item_model.RoiItem): + roi_name = item.roiName() + roi = [r for r in self.__roiManager.getRois() if r.getName() == roi_name] + roi = roi[0] if len(roi) > 0 else None + if roi is not None: + roi.setVisible(item.isVisible()) + + def __updateImageItem(self, item: plot_model.Item): scan = self.__scan plot = self.__plot plotItems: List[_ItemDescription] = [] diff --git a/tests/flint/helper/test_scan_info_helper.py b/tests/flint/helper/test_scan_info_helper.py index a51740133d..0359228b98 100755 --- a/tests/flint/helper/test_scan_info_helper.py +++ b/tests/flint/helper/test_scan_info_helper.py @@ -45,6 +45,7 @@ SCAN_INFO_LIMA_ROIS = { "beamviewer:roi_counters:roi4_avg", "beamviewer:roi_counters:roi5_avg", ], + "images": ["beamviewer:image"], } }, "rois": { @@ -119,7 +120,7 @@ def test_create_scan_model_with_lima_rois(): deviceCount = len(list(scan.devices())) for device in scan.devices(): channelCount += len(list(device.channels())) - assert channelCount == 7 + assert channelCount == 8 assert deviceCount == 7 channel = scan.getChannelByName("beamviewer:roi_counters:roi1_avg") @@ -341,6 +342,18 @@ def test_amesh_scan_with_image_and_mca(): assert result_plots[1].name() == "foo" +def test_create_plot_model_with_rois(): + scan = scan_info_helper.create_scan_model(SCAN_INFO_LIMA_ROIS) + plots = scan_info_helper.infer_plot_models(SCAN_INFO_LIMA_ROIS) + image_plots = [p for p in plots if isinstance(p, plot_item_model.ImagePlot)] + plot = image_plots[0] + roi_items = [i for i in plot.items() if isinstance(i, plot_item_model.RoiItem)] + assert len(roi_items) == 2 + for item in roi_items: + roi = item.roi(scan) + assert roi is not None + + def test_progress_percent_curve(): scan_info = { "npoints": 10, -- GitLab From 6928efbb27143c722baa3c2e4719ea2267fcaeec Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 16:32:34 +0100 Subject: [PATCH 08/13] Provide fullName for devices --- bliss/flint/model/scan_model.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bliss/flint/model/scan_model.py b/bliss/flint/model/scan_model.py index 860098ed09..510e0484c1 100755 --- a/bliss/flint/model/scan_model.py +++ b/bliss/flint/model/scan_model.py @@ -446,6 +446,18 @@ class Device(qt.QObject, _Sealable): def name(self) -> str: return self.__name + def fullName(self): + """Path name from top master to this device. + + Each short name is separated by ":". + """ + elements = [self.name()] + parent = self.__master + while parent is not None: + elements.append(parent.name()) + parent = parent.__master + return ":".join(reversed(elements)) + def setMetadata(self, metadata: DeviceMetadata): if self.isSealed(): raise SealedError() -- GitLab From 98e3239afb3a594f06da7774594fdb67a86288cb Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 16:36:47 +0100 Subject: [PATCH 09/13] Allow to display and hide the ROIs --- bliss/flint/model/plot_item_model.py | 11 +++ bliss/flint/widgets/_property_tree_helper.py | 2 + bliss/flint/widgets/image_plot.py | 2 +- bliss/flint/widgets/image_plot_property.py | 78 +++++++++++++------- 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/bliss/flint/model/plot_item_model.py b/bliss/flint/model/plot_item_model.py index fae19e4e49..2dfaa878f2 100755 --- a/bliss/flint/model/plot_item_model.py +++ b/bliss/flint/model/plot_item_model.py @@ -360,10 +360,21 @@ class RoiItem(plot_model.Item): super(RoiItem, self).__setstate__(state) self.__deviceName = state.pop("deviceName") + def name(self) -> str: + """Returns the name displayed for this item""" + return self.roiName() + def roiName(self) -> str: """Returns the name of the ROI""" return self.__deviceName.split(":")[-1] + def deviceName(self): + """Returns the device name defining this ROI. + + The device name is the full device name prefixed by the top master name + """ + return self.__deviceName + def setDeviceName(self, name: str): """Set the device name containing the ROI. diff --git a/bliss/flint/widgets/_property_tree_helper.py b/bliss/flint/widgets/_property_tree_helper.py index a182e52ad6..d627f46129 100755 --- a/bliss/flint/widgets/_property_tree_helper.py +++ b/bliss/flint/widgets/_property_tree_helper.py @@ -94,6 +94,8 @@ class ScanRowItem(StandardRowItem): icon = icons.getQIcon("flint:icons/channel-spectrum") elif isinstance(plotItem, plot_item_model.ImageItem): icon = icons.getQIcon("flint:icons/channel-image") + elif isinstance(plotItem, plot_item_model.RoiItem): + icon = icons.getQIcon("flint:icons/device-image-roi") elif isinstance(plotItem, plot_item_model.ScatterItem): icon = icons.getQIcon("flint:icons/channel-curve") elif isinstance(plotItem, plot_state_model.CurveStatisticItem): diff --git a/bliss/flint/widgets/image_plot.py b/bliss/flint/widgets/image_plot.py index da7e94115b..2975bc0109 100755 --- a/bliss/flint/widgets/image_plot.py +++ b/bliss/flint/widgets/image_plot.py @@ -613,7 +613,7 @@ class ImagePlotWidget(plot_helper.PlotWidget): item.setEditable(False) item.setSelectable(False) self.__roiManager.addRoi(item) - item.setColor(qt.QColor(0xA0, 0xA0, 0xA0)) + item.setColor(qt.QColor(0x80, 0x80, 0x80)) item.setVisible(False) def __scanDataUpdated(self, event: scan_model.ScanDataUpdateEvent): diff --git a/bliss/flint/widgets/image_plot_property.py b/bliss/flint/widgets/image_plot_property.py index 970205e0ee..f2f85f31c9 100755 --- a/bliss/flint/widgets/image_plot_property.py +++ b/bliss/flint/widgets/image_plot_property.py @@ -39,6 +39,7 @@ class _DataItem(_property_tree_helper.ScanRowItem): self.__plotModel: Optional[plot_model.Plot] = None self.__plotItem: Optional[plot_model.Item] = None self.__channel: Optional[scan_model.Channel] = None + self.__device: Optional[scan_model.Device] = None self.__treeView: Optional[qt.QTreeView] = None self.__flintModel: Optional[flint_model.FlintState] = None @@ -85,9 +86,13 @@ class _DataItem(_property_tree_helper.ScanRowItem): self.__plotItem.setVisible(state == qt.Qt.Checked) def setDevice(self, device: scan_model.Device): + self.__device = device self.setDeviceLookAndFeel(device) self.__used.setCheckable(False) + def device(self): + return self.__device + def setChannel(self, channel: scan_model.Channel): self.__channel = channel self.setChannelLookAndFeel(channel) @@ -98,14 +103,17 @@ class _DataItem(_property_tree_helper.ScanRowItem): def setPlotItem(self, plotItem): self.__plotItem = plotItem - self.__used.modelUpdated = None - self.__used.setData(plotItem, role=delegates.PlotItemRole) - self.__used.setCheckState(qt.Qt.Checked) - self.__used.modelUpdated = weakref.WeakMethod(self.__usedChanged) + isRoiItem = isinstance(plotItem, plot_item_model.RoiItem) - self.__style.setData(plotItem, role=delegates.PlotItemRole) - self.__style.setData(self.__flintModel, role=delegates.FlintModelRole) - self.__remove.setData(plotItem, role=delegates.PlotItemRole) + if not isRoiItem: + self.__used.modelUpdated = None + self.__used.setData(plotItem, role=delegates.PlotItemRole) + self.__used.setCheckState(qt.Qt.Checked) + self.__used.modelUpdated = weakref.WeakMethod(self.__usedChanged) + + self.__style.setData(plotItem, role=delegates.PlotItemRole) + self.__style.setData(self.__flintModel, role=delegates.FlintModelRole) + self.__remove.setData(plotItem, role=delegates.PlotItemRole) if plotItem is not None: isVisible = plotItem.isVisible() @@ -122,10 +130,11 @@ class _DataItem(_property_tree_helper.ScanRowItem): self.setPlotItemLookAndFeel(plotItem) self.__treeView.openPersistentEditor(self.__displayed.index()) - self.__treeView.openPersistentEditor(self.__remove.index()) - if self.__treeView.isPersistentEditorOpen(self.__style.index()): - self.__treeView.closePersistentEditor(self.__style.index()) - self.__treeView.openPersistentEditor(self.__style.index()) + if not isRoiItem: + self.__treeView.openPersistentEditor(self.__remove.index()) + if self.__treeView.isPersistentEditorOpen(self.__style.index()): + self.__treeView.closePersistentEditor(self.__style.index()) + self.__treeView.openPersistentEditor(self.__style.index()) # FIXME: why do we have to do that? self.__treeView.resizeColumnToContents(self.__style.column()) @@ -226,6 +235,7 @@ class ImagePlotPropertyWidget(qt.QWidget): assert self.__plotModel is not None scanTree = {} channelItems: Dict[str, _DataItem] = {} + deviceItems: Dict[str, _DataItem] = {} devices: List[qt.QStandardItem] = [] channelsPerDevices: Dict[qt.QStandardItem, int] = {} @@ -250,6 +260,7 @@ class ImagePlotPropertyWidget(qt.QWidget): parent.appendRow(item.rowItems()) # It have to be done when model index are initialized item.setDevice(device) + deviceItems[device.fullName()] = item devices.append(item) channels = [] @@ -278,17 +289,22 @@ class ImagePlotPropertyWidget(qt.QWidget): break # Clean up unused devices - for device in reversed(devices): - if device not in channelsPerDevices: + for deviceItem in reversed(devices): + device = deviceItem.device() + if device.type() == scan_model.DeviceType.VIRTUAL_ROI: + continue + if device.name() in ["roi_counters", "roi_profiles"]: continue - if channelsPerDevices[device] > 0: + if deviceItem not in channelsPerDevices: continue - parent = device.parent() + if channelsPerDevices[deviceItem] > 0: + continue + parent = deviceItem.parent() if parent is None: parent = model - parent.removeRows(device.row(), 1) + parent.removeRows(deviceItem.row(), 1) - return channelItems + return deviceItems, channelItems def __updateTree(self): # FIXME: expanded/collapsed items have to be restored @@ -321,23 +337,24 @@ class ImagePlotPropertyWidget(qt.QWidget): scan = self.__scan if scan is not None: - channelItems = self.__genScanTree(model, scan, scan_model.ChannelType.IMAGE) + deviceItems, channelItems = self.__genScanTree( + model, scan, scan_model.ChannelType.IMAGE + ) else: - channelItems = {} + deviceItems, channelItems = {}, {} itemWithoutLocation = qt.QStandardItem("Not linked to this scan") model.appendRow(itemWithoutLocation) - plotImageItems = [ - i - for i in self.__plotModel.items() - if isinstance(i, plot_item_model.ImageItem) and i.imageChannel() is not None - ] + plotImageItems = [] + + for plotItem in self.__plotModel.items(): + if isinstance(plotItem, plot_item_model.ImageItem): + plotImageItems.append(plotItem) + dataChannel = plotItem.imageChannel() + if dataChannel is None: + continue - if len(plotImageItems) >= 1: - plotItem = plotImageItems[0] - dataChannel = plotItem.imageChannel() - if dataChannel is not None: dataChannelName = dataChannel.name() if dataChannelName in channelItems: channelItem = channelItems[dataChannelName] @@ -349,6 +366,11 @@ class ImagePlotPropertyWidget(qt.QWidget): # It have to be done when model index are initialized item.setPlotItem(plotItem) + elif isinstance(plotItem, plot_item_model.RoiItem): + deviceItem = deviceItems.get(plotItem.deviceName()) + if deviceItem is not None: + deviceItem.setPlotItem(plotItem) + if len(plotImageItems) >= 2: _logger.warning( "More than one image is provided by this plot. A single image is supported, others will be ignored." -- GitLab From b1fcc058251831046a3166bb173e58f05f49b836 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 18:38:00 +0100 Subject: [PATCH 10/13] Care about the orientation of the arrow --- bliss/flint/widgets/image_plot.py | 4 +++- bliss/flint/widgets/utils/rois.py | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/bliss/flint/widgets/image_plot.py b/bliss/flint/widgets/image_plot.py index 2975bc0109..e85ff9582f 100755 --- a/bliss/flint/widgets/image_plot.py +++ b/bliss/flint/widgets/image_plot.py @@ -590,15 +590,18 @@ class ImagePlotWidget(plot_helper.PlotWidget): item = silx_rois.RectangleROI() origin = roi.x, roi.y size = roi.width, roi.height + self.__roiManager.addRoi(item) item.setGeometry(origin=origin, size=size) elif isinstance(roi, lima_rois.Roi): item = silx_rois.RectangleROI() origin = roi.x, roi.y size = roi.width, roi.height + self.__roiManager.addRoi(item) item.setGeometry(origin=origin, size=size) elif isinstance(roi, lima_rois.ArcRoi): item = silx_rois.ArcROI() center = roi.cx, roi.cy + self.__roiManager.addRoi(item) item.setGeometry( center=center, innerRadius=roi.r1, @@ -612,7 +615,6 @@ class ImagePlotWidget(plot_helper.PlotWidget): item.setName(device.name()) item.setEditable(False) item.setSelectable(False) - self.__roiManager.addRoi(item) item.setColor(qt.QColor(0x80, 0x80, 0x80)) item.setVisible(False) diff --git a/bliss/flint/widgets/utils/rois.py b/bliss/flint/widgets/utils/rois.py index fe3f8dbfbe..8380a1fb5c 100644 --- a/bliss/flint/widgets/utils/rois.py +++ b/bliss/flint/widgets/utils/rois.py @@ -101,6 +101,19 @@ class LimaProfileRoi(silx_rois.RectangleROI): def getLimaKind(self): return self.__limaKind + def _getPlot(self): + manager = self.parent() + if manager is None: + return None + plot = manager.parent() + return plot + + def _isYAxisInverted(self): + plot = self._getPlot() + if plot is not None: + return plot.isYAxisInverted() + return False + def __updateOverlay(self): x, y = self.getCenter() w, h = self.getSize() @@ -109,8 +122,11 @@ class LimaProfileRoi(silx_rois.RectangleROI): points = [[x - w, y], [x + w, y]] symbol = "caretright" elif self.__limaKind == self.Directions.VERTICAL_REDUCTION: - points = [[x, y - h], [x, y + h]] symbol = "caretdown" + if self._isYAxisInverted(): + points = [[x, y - h], [x, y + h]] + else: + points = [[x, y + h], [x, y - h]] else: assert False self.__line.setPoints(points) -- GitLab From 35f58a730f61650a131107db41f0aa9880c9ae7d Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 18:40:17 +0100 Subject: [PATCH 11/13] Round profile ROI shape --- bliss/flint/widgets/utils/rois.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bliss/flint/widgets/utils/rois.py b/bliss/flint/widgets/utils/rois.py index 8380a1fb5c..b6dc79a015 100644 --- a/bliss/flint/widgets/utils/rois.py +++ b/bliss/flint/widgets/utils/rois.py @@ -49,7 +49,7 @@ class LimaArcRoi(silx_rois.ArcROI): """ -class LimaProfileRoi(silx_rois.RectangleROI): +class LimaProfileRoi(LimaRectRoi): """Rectangle ROI used to configure Lima detector. It is used to compute a vertical or horizontal profile. -- GitLab From 09ac92ffcf51ba8fae5943977269b6d59be8f4c6 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 19:31:52 +0100 Subject: [PATCH 12/13] Fix symbol visibility --- bliss/flint/widgets/utils/rois.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bliss/flint/widgets/utils/rois.py b/bliss/flint/widgets/utils/rois.py index b6dc79a015..b61f26bcf4 100644 --- a/bliss/flint/widgets/utils/rois.py +++ b/bliss/flint/widgets/utils/rois.py @@ -80,6 +80,7 @@ class LimaProfileRoi(LimaRectRoi): def _updated(self, event=None, checkVisibility=True): if event in [items.ItemChangedType.VISIBLE]: self._updateItemProperty(event, self, self.__line) + self._updateItemProperty(event, self, self.__symbol) super(LimaProfileRoi, self)._updated(event, checkVisibility) def _updatedStyle(self, event, style): -- GitLab From cdbc91c2595916a731aaca5cf920ac984577295c Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Tue, 8 Dec 2020 19:39:22 +0100 Subject: [PATCH 13/13] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3efb8177..9aacbda198 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Flint: + - Display ROI geometries with the detector image widget - Custom scan description (`scan_info`) - `bliss.scanning.scan_info.ScanInfo` was created in order to replace scan_info dictionary and ScanInfoFactory - A `set_sequence_info` helper was added to define the amount of excepted scans from a sequence + - Creates a `channels` structure for metadata like `display_name` and `unit` + - Provides ROI geometry ### Changed -- GitLab