Commit b1b71941 authored by Matias Guijarro's avatar Matias Guijarro
Browse files

Merge branch '2744-roi-counters-validity' into 'master'

Resolve "Roi counters validity"

Closes #2744 and #2763

See merge request !3682
parents 6fdbf411 20b3f93c
Pipeline #47118 passed with stages
in 123 minutes and 3 seconds
......@@ -7,27 +7,151 @@
from typing import Iterable
import typeguard
from bliss.common.counter import Counter
from bliss.controllers.lima.roi import raw_roi_to_current_roi, current_roi_to_raw_roi
import numpy
from bliss.common.counter import Counter
from bliss.config.beacon_object import BeaconObject
from bliss.common.logtools import log_debug
# ========== RULES of Tango-Lima ==================
# order of image transformation:
# 0) set back binning to 1,1 before any flip or rot modif (else lima crashes if a roi/subarea is already defined))
# 1) flip [Left-Right, Up-Down] (in bin 1,1 only else lima crashes if a roi/subarea is already defined)
# 2) rotation (clockwise and negative angles not possible) (in bin 1,1 only for same reasons)
# 3) binning
# 4) roi (expressed in the current state => roi = f(flip, rot, bin))
# Lima rules and order of image transformations:
# note:
# rot 180 = flip LR + UD
# rot 270 = LR + UD + rot90
# 1) binning
# 2) flip
# 3) rotation
# 4) roi (expressed in the current state f(bin, flip, rot))
# roi is defined in the current image referential (i.e roi = f(rot, flip, bin))
# raw_roi is defined in the raw image referential (i.e with bin=1,1 flip=False,False, rot=0)
# flip = [Left-Right, Up-Down]
# ----------------- helpers for ROI coordinates (x,y,w,h) transformations (flip, rotation, binning) --------------
_DEG2RAD = numpy.pi / 180.0
def current_coords_to_raw_coords(coords_list, img_size, flip, rotation, binning):
if not isinstance(coords_list, numpy.ndarray):
pts = numpy.array(coords_list)
else:
pts = coords_list.copy()
w0, h0 = img_size
# inverse rotation
if rotation != 0:
pts = calc_pts_rotation(pts, -rotation, (w0, h0))
if rotation in [90, 270]:
w0, h0 = img_size[1], img_size[0]
# unflipped roi
if flip[0]:
pts[:, 0] = w0 - pts[:, 0]
if flip[1]:
pts[:, 1] = h0 - pts[:, 1]
# unbinned roi
xbin, ybin = binning
if xbin != 1 or ybin != 1:
pts[:, 0] = pts[:, 0] * xbin
pts[:, 1] = pts[:, 1] * ybin
return pts
def raw_coords_to_current_coords(
raw_coords_list, raw_img_size, flip, rotation, binning
):
if not isinstance(raw_coords_list, numpy.ndarray):
pts = numpy.array(raw_coords_list)
else:
pts = raw_coords_list.copy()
w0, h0 = raw_img_size
# bin roi
xbin, ybin = binning
if xbin != 1 or ybin != 1:
pts[:, 0] = pts[:, 0] / xbin
pts[:, 1] = pts[:, 1] / ybin
w0 = w0 / xbin
h0 = h0 / ybin
# flip roi
if flip[0]:
pts[:, 0] = w0 - pts[:, 0]
if flip[1]:
pts[:, 1] = h0 - pts[:, 1]
# rotate roi
if rotation != 0:
pts = calc_pts_rotation(pts, rotation, (w0, h0))
return pts
def raw_roi_to_current_roi(raw_roi, raw_img_size, flip, rotation, binning):
x, y, w, h = raw_roi
pts = [[x, y], [x + w, y + h]]
pts = raw_coords_to_current_coords(pts, raw_img_size, flip, rotation, binning)
x1, y1 = pts[0]
x2, y2 = pts[1]
x = min(x1, x2)
y = min(y1, y2)
w = abs(x2 - x1)
h = abs(y2 - y1)
return [round(x), round(y), round(w), round(h)]
def current_roi_to_raw_roi(current_roi, img_size, flip, rotation, binning):
x, y, w, h = current_roi
pts = [[x, y], [x + w, y + h]]
pts = current_coords_to_raw_coords(pts, img_size, flip, rotation, binning)
x1, y1 = pts[0]
x2, y2 = pts[1]
x = min(x1, x2)
y = min(y1, y2)
w = abs(x2 - x1)
h = abs(y2 - y1)
return [x, y, w, h]
def calc_pts_rotation(pts, angle, img_size):
if not isinstance(pts, numpy.ndarray):
pts = numpy.array(pts)
# define the camera fullframe
w0, h0 = img_size
frame = numpy.array([[0, 0], [w0, h0]])
# define the rotation matrix
theta = _DEG2RAD * angle * -1 # Lima rotation is clockwise !
R = numpy.array(
[[numpy.cos(theta), -numpy.sin(theta)], [numpy.sin(theta), numpy.cos(theta)]]
)
new_frame = numpy.dot(frame, R)
new_pts = numpy.dot(pts, R)
# find new origin
ox = numpy.amin(new_frame[:, 0])
oy = numpy.amin(new_frame[:, 1])
# apply new origin
new_pts[:, 0] = new_pts[:, 0] - ox
new_pts[:, 1] = new_pts[:, 1] - oy
return new_pts
# -------------------------------------------------------------------------------------------
def _to_list(setting, value):
......@@ -232,10 +356,7 @@ class ImageCounter(Counter):
# handle lazy init
if self._cur_roi is None:
detector_size = self._get_detector_max_size()
self._cur_roi = raw_roi_to_current_roi(
self.raw_roi, detector_size, self.flip, self.rotation, self.binning
)
self._update_roi()
return self._cur_roi
......@@ -248,6 +369,8 @@ class ImageCounter(Counter):
) # store the new _raw_roi in redis/settings
self._cur_roi = roi
self._counter_controller._update_lima_rois()
@property
def subarea(self):
""" Returns the active area of the detector (like 'roi').
......@@ -277,6 +400,7 @@ class ImageCounter(Counter):
self._cur_roi = raw_roi_to_current_roi(
self.raw_roi, detector_size, self.flip, self.rotation, self.binning
)
self._counter_controller._update_lima_rois()
def _calc_raw_roi(self, roi):
""" computes the raw_roi from a given roi and current bin, flip, rot """
......
......@@ -442,11 +442,10 @@ class Lima(CounterController, HasMetadataForScan):
# ------- send the params to tango-lima ---------------------------------------------
# Lima rules and order of image transformations:
# 0) set back binning to 1,1 before any flip or rot modif (else lima crashes if a roi/subarea is already defined with lima-core < 1.9.6rc3))
# 1) flip [Left-Right, Up-Down] (in bin 1,1 only else lima crashes if a roi/subarea is already defined)
# 2) rotation (clockwise and negative angles not possible) (in bin 1,1 only for same reason)
# 3) binning
# 4) roi (expressed in the current state f(flip, rot, bin))
# 1) binning
# 2) flip [Left-Right, Up-Down]
# 3) rotation (clockwise!)
# 4) roi (expressed in the current state f(bin, flip, rot))
# --- Extract special params from ctrl_params and sort them -----------
special_params = {}
......@@ -635,6 +634,7 @@ class Lima(CounterController, HasMetadataForScan):
if self.__roi_counters is None:
roi_counters_proxy = self._get_proxy(self._ROI_COUNTERS)
self.__roi_counters = RoiCounters(roi_counters_proxy, self)
global_map.register(
self.__roi_counters,
parents_list=[self],
......@@ -793,6 +793,18 @@ class Lima(CounterController, HasMetadataForScan):
return info_str
def _update_lima_rois(self):
# do not use the property to avoid recursive calls
if self.__roi_counters is not None:
self.__roi_counters._check_rois_counters() # remove this line to post pone the update at next scan
if self.__roi_profiles is not None:
self.__roi_profiles._check_rois_counters() # remove this line to post pone the update at next scan
if self.__roi_collection is not None:
self.__roi_collection._check_rois_counters() # remove this line to post pone the update at next scan
# Expose counters
@autocomplete_property
......
import os
import datetime
import time
import tabulate
......@@ -13,6 +14,7 @@ from bliss.common.utils import BOLD, RED
from bliss.common.utils import shorten_signature, typeguardTypeError_to_hint
from bliss.data.lima_image import read_video_last_image
from bliss.common.tango import DevFailed
from bliss.common.image_tools import array_to_file
_log = logging.getLogger("bliss.scans")
......@@ -283,6 +285,20 @@ def load_simulator_frames(simulator, nframes, files_pattern):
reset_cam(simulator, roi=[0, 0, 0, 0])
def load_simulator_with_image_data(simulator, arry):
""" Load an image (array) into a Lima simulator.
args:
- simulator: a Lima object of the type simulator
- arry: the data array
"""
img_path = "bliss/tests/images/test_img.edf"
array_to_file(arry.astype("uint32"), img_path)
load_simulator_frames(simulator, 1, img_path)
# os.remove(img_path)
def reset_cam(cam, roi=None):
"""reset lima image parameters and align tango proxy"""
......
This diff is collapsed.
......@@ -12,7 +12,6 @@ from bliss.controllers.lima.limatools import (
from bliss.data.lima_image import image_from_server
from bliss.shell.formatters.table import IncrementalTable
from bliss.common.image_tools import get_image_display
......
......@@ -13,10 +13,12 @@ import gevent
import logging
import numpy
from unittest import mock
from math import pi as _PI_
from bliss.common.utils import all_equal
from bliss.common.image_tools import draw_arc, draw_rect, array_to_file, file_to_array
from bliss.scanning.acquisition.timer import SoftwareTimerMaster
from bliss.common.tango import DeviceProxy, DevFailed
from bliss.common.tango import DevFailed
from bliss.common.counter import Counter
from bliss.controllers.lima.roi import (
Roi,
......@@ -28,12 +30,10 @@ from bliss.controllers.lima.roi import (
from bliss.controllers.lima.roi import RoiProfileCounter, RoiStatCounter
from bliss.common.scans import loopscan, timescan, sct, ct, DEFAULT_CHAIN
from bliss.controllers.lima.limatools import load_simulator_frames, reset_cam
from math import pi as _PI_
from ..conftest import lima_simulator_context
from bliss.config.channels import Cache
import time
def test_lima_simulator(beacon, lima_simulator):
simulator = beacon.get("lima_simulator")
......@@ -212,7 +212,7 @@ def test_arc_rois(beacon, default_session, lima_simulator, images_directory):
cam.roi_counters["a1"] = 316, 443, 50, 88, -120, -180
cam.roi_counters["a2"] = 130, 320, 0, radius, 0, 360
s = ct(cam)
s = ct(0.1, cam)
assert s.get_data("a1_sum")[0] == 0.0
......@@ -274,7 +274,7 @@ def test_lima_roi_counters_api(beacon, default_session, lima_simulator):
# perform a scan to push rois to TangoDevice (roi_ids are retrieved at that time)
assert len(cam.roi_counters._roi_ids) == 0
ct(cam)
ct(0.1, cam)
assert len(cam.roi_counters._roi_ids) == 5
# del one roi
......@@ -298,6 +298,188 @@ def test_lima_roi_counters_api(beacon, default_session, lima_simulator):
assert len(cam.roi_counters.counters) == 0
def test_lima_roi_counters_measurements(
beacon, default_session, lima_simulator, images_directory
):
cam = beacon.get("lima_simulator")
cam.roi_counters.clear()
img_path = os.path.join(str(images_directory), "arc.edf")
# generate test image and save as file
# arc rois
cx, cy = 250, 350
r1, r2 = 80, 100
a1, a2 = 10, 45
arry = numpy.ones((800, 600))
arry = draw_arc(arry, cx, cy, r1, r2, a1, a2, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 90, a2 + 90, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 180, a2 + 180, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 270, a2 + 270, fill_value=0)
arry = draw_arc(arry, cx, cy, r2 + 10, r2 + 20, 10, 100, fill_value=0)
arry = draw_arc(arry, cx, cy, r2 + 30, r2 + 40, 10, 190, fill_value=0)
arry = draw_arc(arry, cx, cy, r2 + 50, r2 + 60, 10, 260, fill_value=0)
arry = draw_arc(arry, cx, cy, r2 + 70, r2 + 80, 10, 350, fill_value=0)
# rect roi
w0, h0 = 60, 30
arry = draw_rect(arry, cx, cy, w0, h0, fill_value=0)
array_to_file(arry.astype("uint32"), img_path)
# load simulator with test image
load_simulator_frames(cam, 1, img_path)
ox, oy, ow, oh = 10, 50, 500, 650
reset_cam(cam, roi=[ox, oy, ow, oh])
cx, cy = cx - ox, cy - oy # take into account the image roi offset
debug = 0
if debug:
import matplotlib.pyplot as plt
plt.imshow(file_to_array(img_path))
plt.show()
from bliss.shell.standard import flint
pf = flint()
cam.roi_counters["a1"] = cx, cy, r1, r2, a1, a2
cam.roi_counters["a2"] = cx, cy, r1, r2, a1 + 90, a2 + 90
cam.roi_counters["a3"] = cx, cy, r1, r2, a1 + 180, a2 + 180
cam.roi_counters["a4"] = cx, cy, r1, r2, a1 + 270, a2 + 270
cam.roi_counters["a5"] = cx, cy, r2 + 10, r2 + 20, 10, 100
cam.roi_counters["a6"] = cx, cy, r2 + 30, r2 + 40, 10, 190
cam.roi_counters["a7"] = cx, cy, r2 + 50, r2 + 60, 10, 260
cam.roi_counters["a8"] = cx, cy, r2 + 70, r2 + 80, 10, 350
cam.roi_counters["r1"] = cx, cy, w0, h0
s = ct(0.01, cam)
assert s.get_data("a1_sum")[0] == 0.0
assert s.get_data("a2_sum")[0] == 0.0
assert s.get_data("a3_sum")[0] == 0.0
assert s.get_data("a4_sum")[0] == 0.0
assert s.get_data("r1_sum")[0] == 0.0
if debug:
pf.wait_end_of_scans()
time.sleep(1)
def test_lima_roi_validity(beacon, default_session, lima_simulator, images_directory):
cam = beacon.get("lima_simulator")
cam.roi_counters.clear()
cam.roi_profiles.clear()
cam.roi_collection.clear()
img_path = os.path.join(str(images_directory), "arc.edf")
# generate test image and save as file
# arc rois
cx, cy = 250, 350
r1, r2 = 80, 100
a1, a2 = 10, 45
arry = numpy.ones((800, 600))
arry = draw_arc(arry, cx, cy, r1, r2, a1, a2, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 90, a2 + 90, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 180, a2 + 180, fill_value=0)
arry = draw_arc(arry, cx, cy, r1, r2, a1 + 270, a2 + 270, fill_value=0)
# rect roi
w0, h0 = 60, 30
arry = draw_rect(arry, cx, cy, w0, h0, fill_value=0)
# roi profile
x1, y1, w1, h1 = 500, 400, 60, 30
arry = draw_rect(arry, x1, y1, w1, h1, fill_value=0)
# roi collection
w2, h2 = 6, 4
collec = {}
nx, ny = 3, 3
for j in range(ny):
for i in range(nx):
x = i * 2 * w2 + 500
y = j * 2 * h2 + 100
arry = draw_rect(arry, x, y, w2, h2, fill_value=0)
collec[f"c{nx*j+i}"] = [x, y, w2, h2]
array_to_file(arry.astype("uint32"), img_path)
debug = 0
if debug:
import matplotlib.pyplot as plt
plt.imshow(file_to_array(img_path))
plt.show()
from bliss.shell.standard import flint
pf = flint()
# load simulator with test image
load_simulator_frames(cam, 1, img_path)
reset_cam(cam, roi=[0, 0, 0, 0])
cam.roi_counters["a1"] = cx, cy, r1, r2, a1, a2
cam.roi_counters["a2"] = cx, cy, r1, r2, a1 + 90, a2 + 90
cam.roi_counters["a3"] = cx, cy, r1, r2, a1 + 180, a2 + 180
cam.roi_counters["a4"] = cx, cy, r1, r2, a1 + 270, a2 + 270
cam.roi_counters["r1"] = cx, cy, w0, h0
cam.roi_profiles["p1"] = x1, y1, w1, h1
for k, v in collec.items():
cam.roi_collection[k] = v
assert len(list(cam.roi_counters.counters)) == 5 * 5 # because 5 counters per roi
assert len(list(cam.roi_profiles.counters)) == 1
assert len(list(cam.roi_collection.counters)) == 1 # one for the collection of rois
# applying this roi should discard 2 rois
cam.image.roi = 0, 0, 240, 500
if debug:
s = ct(0.01, cam)
pf.wait_end_of_scans()
time.sleep(1)
assert len(list(cam.roi_counters.counters)) == 2 * 5
assert len(list(cam.roi_profiles.counters)) == 0
assert (
len(list(cam.roi_collection.counters)) == 0
) # if no rois in collection then no counter
# back to full frame should re-activate the 2 rois discarde previously
cam.image.roi = 0, 0, 0, 0
if debug:
s = ct(0.01, cam)
pf.wait_end_of_scans()
time.sleep(1)
assert len(list(cam.roi_counters.counters)) == 5 * 5
assert len(list(cam.roi_profiles.counters)) == 1
assert len(list(cam.roi_collection.counters)) == 1
assert len(cam.roi_collection.get_rois()) == nx * ny
cam.roi_collection.clear()
assert len(cam.roi_collection.get_rois()) == 0
assert len(list(cam.roi_collection.counters)) == 0
cam.roi_profiles.clear()
assert len(cam.roi_profiles.get_rois()) == 0
assert len(list(cam.roi_profiles.counters)) == 0
cam.roi_counters.clear()
assert len(cam.roi_counters.get_rois()) == 0
assert len(list(cam.roi_counters.counters)) == 0
def test_lima_roi_profiles_api(beacon, default_session, lima_simulator):
cam = beacon.get("lima_simulator")
......@@ -348,7 +530,7 @@ def test_lima_roi_profiles_api(beacon, default_session, lima_simulator):
# perform a scan to push rois to TangoDevice (roi_ids are retrieved at that time)
assert len(cam.roi_profiles._roi_ids) == 0
ct(cam)
ct(0.1, cam)
assert len(cam.roi_profiles._roi_ids) == 5
# check get_roi_mode/set_roi_mode
......@@ -422,11 +604,24 @@ def test_lima_roi_profile_measurements(
# H H H H H
# H H H H H
# breakpoint()
cam = beacon.get("lima_simulator")
img_path = os.path.join(str(images_directory), "chart_3.edf")
load_simulator_frames(cam, 1, img_path)
reset_cam(cam, roi=[0, 0, 0, 0])
debug = 0
if debug:
import matplotlib.pyplot as plt
plt.imshow(file_to_array(img_path))
plt.show()
from bliss.shell.standard import flint
pf = flint()
cam.roi_profiles.clear()
cam.roi_profiles["sp1"] = [20, 20, 18, 20]
cam.roi_profiles["sp2"] = [60, 20, 38, 40]
......@@ -440,7 +635,11 @@ def test_lima_roi_profile_measurements(
# (mode=0, pixels are summed along the vertical axis and the spectrum is along horizontal axis)
cam.roi_profiles.set_roi_mode("horizontal", "sp1", "sp2")
s = ct(cam)
s = ct(0.1, cam)
if debug:
pf.wait_end_of_scans()
time.sleep(1)
d1 = s.get_data("sp1")[0]
d2 = s.get_data("sp2")[0]
......@@ -458,7 +657,12 @@ def test_lima_roi_profile_measurements(
cam.roi_profiles.set_roi_mode("vertical", "sp1")
cam.roi_profiles.set_roi_mode("vertical", "sp2")
s = ct(cam)
s = ct(0.1, cam)
if debug:
pf.wait_end_of_scans()
time.sleep(1)
d1 = s.get_data("sp1")[0]
d2 = s.get_data("sp2")[0]
......@@ -476,7 +680,10 @@ def test_lima_roi_profile_measurements(
cam.roi_profiles.set_roi_mode("vertical", "sp1")
cam.roi_profiles.set_roi_mode("horizontal", "sp2")
s = ct(cam)
s = ct(0.1, cam)
if debug:
pf.wait_end_of_scans()
time.sleep(1)
d1 = s.get_data("sp1")[0]
d2 = s.get_data("sp2")[0]
......@@ -1070,7 +1277,6 @@ def test_roi_profile_devfailed(default_session, lima_simulator, caplog):
def test_roi_collection(default_session, lima_simulator, tmp_path):
from shutil import rmtree
from bliss.common.image_tools import array_to_file, file_to_array
defdtype = numpy.int32 # uint8 # int32
......@@ -1128,7 +1334,6 @@ def test_roi_collection(default_session, lima_simulator, tmp_path):
# load rois collection
collec.clear()
# print("===Collec rois:", list(collec._save_rois.keys()))
for name, roi in rois.items():
# print(f"Load roi {name} {roi.get_coords}")
collec