Commit 1beb2e31 authored by Valentin Valls's avatar Valentin Valls
Browse files

Use _BaseRoi inside Flint RPC

parent 623540e2
......@@ -143,9 +143,6 @@ The return values are shown in the following example:
"""
from typing import List
from typing import Optional
from typing import Tuple
from typing import Any
import numpy
import functools
......@@ -493,62 +490,3 @@ def get_plot(
return flint_plots.ImagePlot(flint=flint, plot_id=plot_id)
else:
print("Argument plot_type uses an invalid value: '%s'." % plot_type)
def convert_roi_to_flint(roi):
"""Convert a received ROI from BLISS to Flint
FIXME: Remove it, the interaction between flint and bliss should use pickle
to improve flexibility
Arguments:
roi: A ROI object from BLISS
"""
# Avoid cyclic import
from bliss.controllers.lima import roi as lima_roi
if isinstance(roi, lima_roi.Roi):
result = dict(
kind="Rectangle",
origin=(roi.x, roi.y),
size=(roi.width, roi.height),
label=roi.name,
)
if isinstance(roi, lima_roi.RoiProfile):
assert roi.mode in ["horizontal", "vertical"]
result["reduction"] = roi.mode + "_profile"
elif isinstance(roi, lima_roi.ArcRoi):
result = dict(kind="Arc", label=roi.name)
result.update(roi.to_dict())
return result
def convert_roi_to_bliss(roi) -> Optional[Tuple[str, Any, Optional[str]]]:
"""Convert a received ROIs from Flint to BLISS
FIXME: Remove it, the interaction between flint and bliss should use pickle
to improve flexibility
"""
label = roi["label"]
if not label:
FLINT_LOGGER.warning("ROI without name. Skipped.")
return None
kind = roi["kind"].lower()
if kind == "rectangle":
x, y = map(int, map(round, roi["origin"]))
w, h = map(int, map(round, roi["size"]))
mode = roi.get("reduction", None)
if mode not in [None, "vertical_profile", "horizontal_profile"]:
FLINT_LOGGER.warning(
"ROI %s with unknown reduction mode %s. Skipped.", label, mode
)
return None
if mode is not None:
mode = mode.replace("_profile", "")
return label, (x, y, w, h), mode
elif kind == "arc":
keys = ("cx", "cy", "r1", "r2", "a1", "a2")
roi_tuple = tuple(roi[c] for c in keys)
return label, roi_tuple, None
FLINT_LOGGER.warning("ROI %s with unknown kind %s. Skipped.", label, kind)
return None
......@@ -195,14 +195,63 @@ class BasePlot(object):
def select_shapes(
self,
initial_selection=(),
initial_selection: typing.Optional[typing.List[typing.Any]] = None,
kinds: typing.Union[str, typing.List[str]] = "rectangle",
use_dict_as_result=True,
):
"""
Request user selection of shapes.
`initial_selection` is a list of ROIs from `bliss.controllers.lima.roi`.
For compatibility, a rectangle ROI still can be described as a key-value
dictionary. It contains "kind" (which is "Rectangle"), and "label",
"origin" and "size" which are tuples of 2 floats.
Arguments:
initial_selection: List of shapes already selected.
kinds: Kind of shapes the user can create
use_dict_as_result: If True, rectangle ROIs are passed as a
dictionary for compatibility. If false, a list of `bliss.controllers.lima.roi`
only is always returned.
"""
from bliss.controllers.lima import roi as lima_roi
flint = self._flint
if initial_selection is not None:
initial_selection = list(initial_selection)
# Retro compatibility
for i, roi in enumerate(initial_selection):
if not isinstance(roi, dict):
continue
kind = roi["kind"].lower()
assert kind == "rectangle"
x, y = map(int, map(round, roi["origin"]))
w, h = map(int, map(round, roi["size"]))
name = roi.get["label"]
roi = lima_roi.Roi(x, y, w, h, name)
initial_selection[i] = roi
request_id = flint.request_select_shapes(
self._plot_id, initial_selection, kinds=kinds
)
return self._wait_for_user_selection(request_id)
result = self._wait_for_user_selection(request_id)
if use_dict_as_result:
# Retro compatibility
for i, roi in enumerate(result):
if not type(roi) == lima_roi.Roi:
continue
roi = dict(
kind="Rectangle",
origin=(roi.x, roi.y),
size=(roi.width, roi.height),
label=roi.name,
)
result[i] = roi
return result
def select_points(self, nb):
flint = self._flint
......
......@@ -28,6 +28,7 @@ from silx.gui import qt
from silx.gui import plot as silx_plot
import bliss
from bliss.flint.helper import plot_interaction, scan_info_helper
from bliss.controllers.lima import roi as lima_roi
from bliss.flint.helper import model_helper
from bliss.flint.model import plot_model
from bliss.flint.model import plot_item_model
......@@ -577,25 +578,18 @@ class FlintApi:
def request_select_shapes(
self,
plot_id,
initial_shapes: Sequence[Dict] = (),
initial_shapes: Sequence[lima_roi._BaseRoi] = (),
kinds: Union[str, List[str]] = "rectangle",
timeout=None,
) -> str:
"""
Request a shape selection in a specific plot and return the selection.
A shape is described by a dictionary according to it's kind:
- For a rectangle it contains "kind" (which is "Rectangle"), and "label",
"origin" and "size"
- A rectangle can also contain a "reduction" key, which can be one of
"vertical_profile" or "horizontal_profile"
- For an arc it contains "kind" (which is "Arc"), and "label",
"c1", "c2", "r1", "r2", "a1", "a2" (clockwise, in degree)
A shape is described as a ROI object from `bliss.controllers.lima.roi`.
Arguments:
plot_id: Identifier of the plot
initial_shapes: A list of shapes describing the current selection.
Only rectangles and arcs are supported.
timeout: A timeout to enforce the user to do a selection
kinds: List or ROI kind which can be created (for now, "rectangle",
"arc", "rectangle-vertical-profile", "rectangle-horizontal-profile")
......
......@@ -8,6 +8,7 @@
from __future__ import annotations
from typing import Dict
from typing import Sequence
from typing import Optional
import numpy
import logging
......@@ -23,6 +24,8 @@ from silx.gui.plot.items.roi import ArcROI
from silx.gui.plot.items.roi import PointROI
from silx.gui.plot.items.roi import RegionOfInterest
from silx.gui.plot.tools.roi import RegionOfInterestManager
from bliss.controllers.lima import roi as lima_roi
from bliss.flint.widgets.roi_selection_widget import RoiSelectionWidget
from bliss.flint.widgets.utils import rois as extra_rois
......@@ -463,7 +466,11 @@ class ShapesSelector(Selector):
roiClass = self.__mapping[kind]
self.__kinds.append(roiClass)
def setInitialShapes(self, initialShapes: Sequence[Dict] = ()):
def setInitialShapes(
self, initialShapes: Optional[Sequence[lima_roi._BaseRoi]] = None
):
if initialShapes is None:
initialShapes = []
self.__initialShapes = initialShapes
def setTimeout(self, timeout):
......@@ -472,64 +479,68 @@ class ShapesSelector(Selector):
def __dictToRois(self, shapes: Sequence[Dict]) -> Sequence[RegionOfInterest]:
rois = []
for shape in shapes:
kind = shape["kind"].lower()
if kind == "rectangle":
reduction = shape.get("reduction", None)
if reduction is None:
roi = RectangleROI()
elif reduction == "horizontal_profile":
if isinstance(shape, lima_roi.RoiProfile):
if shape.mode == "horizontal":
roi = extra_rois.VerticalReductionLimaRoi()
elif reduction == "vertical_profile":
elif shape.mode == "vertical":
roi = extra_rois.HorizontalReductionLimaRoi()
roi.setGeometry(origin=shape["origin"], size=shape["size"])
roi.setName(shape["label"])
rois.append(roi)
elif kind == "arc":
else:
_logger.error("RoiProfile mode '%s' unsupported", roi.mode)
continue
roi.setGeometry(
origin=(shape.x, shape.y), size=(shape.width, shape.height)
)
elif isinstance(shape, lima_roi.Roi):
roi = RectangleROI()
roi.setGeometry(
origin=(shape.x, shape.y), size=(shape.width, shape.height)
)
elif isinstance(shape, lima_roi.ArcRoi):
roi = ArcROI()
roi.setGeometry(
center=(shape["cx"], shape["cy"]),
innerRadius=shape["r1"],
outerRadius=shape["r2"],
startAngle=numpy.deg2rad(shape["a1"]),
endAngle=numpy.deg2rad(shape["a2"]),
center=(shape.cx, shape.cy),
innerRadius=shape.r1,
outerRadius=shape.r2,
startAngle=numpy.deg2rad(shape.a1),
endAngle=numpy.deg2rad(shape.a2),
)
roi.setName(shape["label"])
rois.append(roi)
else:
raise ValueError(f"Unknown shape of type {kind}")
_logger.error("Unknown ROI kind '%s'", type(roi))
continue
roi.setName(shape.name)
rois.append(roi)
return rois
def __roisToDict(self, rois: Sequence[RegionOfInterest]) -> Sequence[Dict]:
shapes = []
for roi in rois:
if isinstance(roi, RectangleROI):
shape = dict(
origin=roi.getOrigin(),
size=roi.getSize(),
label=roi.getName(),
kind="Rectangle",
)
x, y = roi.getOrigin()
w, h = roi.getSize()
name = roi.getName()
if isinstance(roi, extra_rois.VerticalReductionLimaRoi):
shape["reduction"] = "horizontal_profile"
mode = "horizontal"
shape = lima_roi.RoiProfile(x, y, w, h, mode=mode, name=name)
elif isinstance(roi, extra_rois.HorizontalReductionLimaRoi):
shape["reduction"] = "vertical_profile"
shapes.append(shape)
mode = "vertical"
shape = lima_roi.RoiProfile(x, y, w, h, mode=mode, name=name)
else:
shape = lima_roi.Roi(x, y, w, h, name=name)
elif isinstance(roi, ArcROI):
shape = dict(
cx=roi.getCenter()[0],
cy=roi.getCenter()[1],
r1=roi.getInnerRadius(),
r2=roi.getOuterRadius(),
a1=numpy.rad2deg(roi.getStartAngle()),
a2=numpy.rad2deg(roi.getEndAngle()),
label=roi.getName(),
kind="Arc",
)
shapes.append(shape)
cx = roi.getCenter()[0]
cy = roi.getCenter()[1]
r1 = roi.getInnerRadius()
r2 = roi.getOuterRadius()
a1 = numpy.rad2deg(roi.getStartAngle())
a2 = numpy.rad2deg(roi.getEndAngle())
name = roi.getName()
shape = lima_roi.ArcRoi(cx, cy, r1, r2, a1, a2, name=name)
else:
_logger.error(
"Unsupported ROI kind %s. ROI skipped from results", type(roi)
)
continue
shapes.append(shape)
return shapes
def start(self):
......
......@@ -916,12 +916,8 @@ def edit_roi_counters(detector: Lima, acq_time: Optional[float] = None):
# Retrieve all the ROIs
selections = []
for roi in roi_counters.get_rois():
roi_dict = plot_module.convert_roi_to_flint(roi)
selections.append(roi_dict)
for roi in roi_profiles.get_rois():
roi_dict = plot_module.convert_roi_to_flint(roi)
selections.append(roi_dict)
selections.extend(roi_counters.get_rois())
selections.extend(roi_profiles.get_rois())
deviceName = (
f"{detector.name} [{roi_counters.config_name}, {roi_profiles.config_name}]"
......@@ -935,21 +931,18 @@ def edit_roi_counters(detector: Lima, acq_time: Optional[float] = None):
"rectangle-vertical-profile",
"rectangle-horizontal-profile",
],
use_dict_as_result=False,
)
result = [plot_module.convert_roi_to_bliss(r) for r in selections]
result = [r for r in result if r is not None]
roi_counters.clear()
roi_profiles.clear()
for name, roi, mode in result:
if mode is None:
roi_counters[name] = roi
for roi in selections:
if isinstance(roi, lima_roi.RoiProfile):
roi_profiles[roi.name] = roi
else:
roi_profiles[name] = roi
roi_profiles.set_roi_mode(mode, name)
roi_counters[roi.name] = roi
roi_string = ", ".join(sorted([r[0] for r in result]))
roi_string = ", ".join(sorted([s.name for s in selections]))
print(f"Applied ROIS {roi_string} to {deviceName}")
......
......@@ -2,6 +2,7 @@
import gevent
from bliss.common import plot
from bliss.controllers.lima import roi as lima_roi
def test_select_points(flint_session):
......@@ -110,13 +111,10 @@ def test_select_shapes__arc(flint_session):
result = context[0]
assert len(result) == 1
roi = result[0]
assert isinstance(roi, dict)
expected_keys = set(["cx", "cy", "r1", "r2", "a1", "a2"])
assert len(expected_keys - roi.keys()) == 0
assert roi["kind"] == "Arc"
assert isinstance(roi, lima_roi.ArcRoi)
def test_select_shapes__rect_reduction(flint_session):
def test_select_shapes__rect_profile(flint_session):
flint = plot.get_flint()
p = plot.plot()
context = []
......@@ -141,8 +139,5 @@ def test_select_shapes__rect_reduction(flint_session):
result = context[0]
assert len(result) == 1
roi = result[0]
assert isinstance(roi, dict)
expected_keys = set(["origin", "size", "label", "kind", "reduction"])
assert len(expected_keys - roi.keys()) == 0
assert roi["reduction"]
assert roi["kind"] == "Rectangle"
assert isinstance(roi, lima_roi.RoiProfile)
assert roi.mode == "vertical"
......@@ -3,11 +3,13 @@
import logging
import pytest
import numpy
import pickle
from silx.gui import qt # noqa: F401
from silx.gui.utils.testutils import TestCaseQt
from bliss.common import plot
from bliss.flint.client import plots
from bliss.controllers.lima import roi as lima_roi
logger = logging.getLogger(__name__)
......@@ -42,3 +44,13 @@ class TestFlint(TestCaseQt):
widget.select_data("sin", "cos", color="green", symbol="x")
widget.deselect_data("sin", "cos")
widget.clear_data()
def test_used_object():
"""Make sure object shared in the RPC are still picklable"""
roi = lima_roi.ArcRoi(0, 1, 2, 3, 4, 5, 6)
pickle.loads(pickle.dumps(roi))
roi = lima_roi.Roi(0, 1, 2, 3)
pickle.loads(pickle.dumps(roi))
roi = lima_roi.RoiProfile(0, 1, 2, 3, mode="vertical")
pickle.loads(pickle.dumps(roi))
......@@ -12,6 +12,7 @@ from bliss.shell.standard import sin, cos, tan, arcsin, arccos, arctan, arctan2
from bliss.shell.standard import log, log10, sqrt, exp, power, deg2rad, rad2deg
from bliss.shell.standard import rand, date, sleep
from bliss.shell.standard import flint
from bliss.controllers.lima import roi as lima_rois
@pytest.fixture
......@@ -324,15 +325,9 @@ def test_edit_roi_counters(
):
class PlotMock:
def select_shapes(self, *args, **kwargs):
roi = dict(kind="Rectangle", origin=(10, 11), size=(100, 101), label="roi1")
roi2spectrum = dict(
kind="Rectangle",
origin=(20, 21),
size=(200, 201),
label="roi1",
reduction="vertical",
)
return [roi, roi2spectrum]
roi1 = lima_rois.Roi(10, 11, 100, 101, name="roi1")
roi2 = lima_rois.RoiProfile(20, 21, 200, 201, name="roi2", mode="vertical")
return [roi1, roi2]
# Mock few functions to coverage the code without flint
mocker.patch("bliss.common.plot.plot_image", return_value=PlotMock())
......@@ -341,11 +336,8 @@ def test_edit_roi_counters(
cam.roi_counters.clear()
cam.roi_profiles.clear()
cam.roi_counters["foo1"] = [20, 20, 18, 20]
cam.roi_profiles["foo1"] = [20, 20, 18, 20]
cam.roi_counters["foo1"] = 20, 20, 18, 20
cam.roi_profiles["foo2"] = 20, 20, 18, 20, "vertical"
standard.edit_roi_counters(cam)
assert "roi1" in cam.roi_counters
assert cam.roi_counters["roi1"].width == 100
assert "roi1" in cam.roi_profiles
assert cam.roi_profiles["roi1"].width == 200
assert cam.roi_profiles.get_roi_modes()["roi1"] == "vertical"
assert "roi2" in cam.roi_profiles
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment