Commit 157148f8 authored by Perceval Guillou's avatar Perceval Guillou
Browse files

introduce scan simulation tools

parent bb2a3b61
Pipeline #47062 passed with stages
in 109 minutes and 29 seconds
......@@ -31,49 +31,81 @@ def file_to_array(fpath):
ext = fpath[fpath.rfind(".") + 1 :]
if ext == "edf":
arry = _fabio_file_to_array(fpath)
size = arry.shape[1], arry.shape[0]
mode = _find_array_mode(arry.shape, arry.dtype)
else:
pil = Image.open(fpath)
if pil.mode == "LA":
pil = pil.convert("L")
elif pil.mode == "I;16B":
pil = pil.convert("I")
pil = _file_to_pil(fpath)
arry = pil_to_array(pil)
return arry
mode = pil.mode
size = pil.size
data = pil.tostring()
arry = buffer_to_array(mode, size, data)
def file_to_pil(fpath):
ext = fpath[fpath.rfind(".") + 1 :]
if ext == "edf":
arry = _fabio_file_to_array(fpath)
pil = array_to_pil(arry)
else:
pil = _file_to_pil(fpath)
return (mode, size, arry)
return pil
def file_to_buffer(fpath):
ext = fpath[fpath.rfind(".") + 1 :]
if ext == "edf":
arry = _fabio_file_to_array(fpath)
data = arry.tostring() # tobytes() ?
size = arry.shape[1], arry.shape[0]
mode = _find_array_mode(arry.shape, arry.dtype)
return array_to_buffer(arry)
else:
pil = Image.open(fpath)
if pil.mode == "LA":
pil = pil.convert("L")
elif pil.mode == "I;16B":
pil = pil.convert("I")
elif pil.mode == "P":
pil = pil.convert("RGB")
pil = _file_to_pil(fpath)
return pil_to_buffer(pil)
def array_to_file(arry, fpath, mode=None):
ext = fpath[fpath.rfind(".") + 1 :]
if ext == "edf":
_fabio_array_to_file(arry, fpath)
else:
if mode is None:
mode = _find_array_mode(arry.shape, arry.dtype)
pil = Image.fromarray(arry, mode)
pil.save(fpath)
mode = pil.mode
size = pil.size
data = pil.tostring()
def array_to_pil(arry, mode=None):
if mode is None:
mode = _find_array_mode(arry.shape, arry.dtype)
return Image.fromarray(arry, mode)
def array_to_buffer(arry, mode=None):
if mode is None:
mode = _find_array_mode(arry.shape, arry.dtype)
size = arry.shape[1], arry.shape[0]
data = arry.tostring()
return (mode, size, data)
def pil_to_file(pil, fpath):
pil.save(fpath)
def pil_to_array(pil):
return buffer_to_array(*pil_to_buffer(pil))
def pil_to_buffer(pil):
if hasattr(pil, "tostring"):
data = pil.tostring() # PIL
else:
data = pil.tobytes() # PILOW
return (pil.mode, pil.size, data)
def buffer_to_file(mode, size, data, fpath):
arry = buffer_to_array(mode, size, data)
array_to_file(arry, fpath)
def buffer_to_array(mode, size, data):
w, h = size
if mode == "RGB":
......@@ -84,30 +116,9 @@ def buffer_to_array(mode, size, data):
return np.frombuffer(data, NUMPY_MODES[mode]).reshape((h, w))
def pil_to_array(pil_img):
w, h = pil_img.size
mode = pil_img.mode
data = pil_img.tostring()
if mode == "RGB":
arry = np.frombuffer(data, NUMPY_MODES[mode]).reshape((h, w, 3))
elif mode == "RGBA":
arry = np.frombuffer(data, NUMPY_MODES[mode]).reshape((h, w, 4))
else:
arry = np.frombuffer(data, NUMPY_MODES[mode]).reshape((h, w))
return arry
def array_to_file(arry, fpath, mode=None):
if mode is None:
mode = _find_array_mode(arry.shape, arry.dtype)
pil = Image.fromarray(arry, mode)
ext = fpath[fpath.rfind(".") + 1 :]
if ext == "edf":
_fabio_array_to_file(arry, fpath)
else:
pil.save(fpath)
def buffer_to_pil(mode, size, data):
arry = buffer_to_array(mode, size, data)
return array_to_pil(arry)
def _fabio_array_to_file(arry, fpath):
......@@ -148,47 +159,37 @@ def _find_array_mode(shape, dtype):
)
# ------ ARRAY CREATION -----------------------
def empty_array(size, mode):
w, h = size
if mode == "RGBA":
shape = (h, w, 4)
elif mode == "RGB":
shape = (h, w, 3)
else:
shape = (h, w)
return np.empty(shape, NUMPY_MODES[mode])
def _file_to_pil(fpath, raw=False):
pil = Image.open(fpath)
if not raw:
if pil.mode == "LA":
pil = pil.convert("L")
elif pil.mode == "I;16B":
pil = pil.convert("I")
elif pil.mode == "P":
pil = pil.convert("RGB")
return pil
def zero_array(size, mode):
w, h = size
if mode == "RGBA":
shape = (h, w, 4)
elif mode == "RGB":
shape = (h, w, 3)
else:
shape = (h, w)
return np.zeros(shape, NUMPY_MODES[mode])
# ------ ARRAY CREATION -----------------------
def gauss2d(w, h, A=100, sx=10, sy=10, cx=0, cy=0):
def gauss2d(w, h, A=100, sx=10, sy=10, cx=None, cy=None):
""" Create a 2D Gaussian array
- w: image width
- h: image height
- A: Gaussian amplitude (max)
- sx: Gaussian sigma along x axis
- sx: Gaussian sigma along y axis
- cx: Gaussian position along x axis (centred by default)
- cy: Gaussian position along y axis (centred by default)
- cx: Gaussian center along x axis (image center by default)
- cy: Gaussian center along y axis (image center by default)
"""
if cx is None:
cx = w / 2
cx += w / 2
cy += h / 2
if cy is None:
cy = h / 2
x = np.linspace(0, w - 1, w)
y = np.linspace(0, h - 1, h)
......@@ -199,79 +200,65 @@ def gauss2d(w, h, A=100, sx=10, sy=10, cx=0, cy=0):
)
# ------ DRAW IN ARRAY ------------------------
def DrawArc(arry, value=1, cx=0, cy=0, r1=100, r2=120, a1=0, a2=180):
# check input args
if r1 < 0 or r2 < 0:
raise ValueError("radius must be a positive number !")
if a1 < 0 or a2 < 0 or a1 > 360 or a2 > 360:
raise ValueError("angles must be in [0, 360] degree !")
h, w = arry.shape
cx = w / 2 + cx
cy = h / 2 + cy
y, x = np.ogrid[:h, :w]
# take the region between the 2 radius
rmini = min(r1, r2)
rmaxi = max(r1, r2)
a = (x - cx) ** 2 + (y - cy) ** 2 <= rmaxi ** 2
b = (x - cx) ** 2 + (y - cy) ** 2 >= rmini ** 2
# take the region between the 2 angles
# Numpy handles y/x where x[i] == 0 => inf and np.arctan(inf) ==> pi/2
def arcmask(w, h, cx, cy, r1, r2, a1, a2):
x = np.linspace(0, w - 1, w)
y = np.linspace(0, h - 1, h)
x, y = np.meshgrid(x, y)
amini = min(a1, a2)
amaxi = max(a1, a2)
radius = (x - cx) ** 2 + (y - cy) ** 2
c1 = (radius >= r1 ** 2) * (radius <= r2 ** 2)
z = (y - cy) / (x - cx)
angles = (np.arctan2((y - cy), (x - cx)) / DEG2RAD) % 360
c2 = (angles >= a1) * (angles <= a2)
if amini < 180 and amaxi > 180:
c = np.arctan2(y - cy, x - cx) >= (amini * DEG2RAD)
d = np.arctan2(y - cy, x - cx) <= (180 * DEG2RAD)
return c1 * c2
amaxi -= 360
e = np.arctan2(y - cy, x - cx) >= (-180 * DEG2RAD)
f = np.arctan2(y - cy, x - cx) <= (amaxi * DEG2RAD)
mask1 = a * b * c * d
mask2 = a * b * e * f
# ------ DRAW IN ARRAY ------------------------
def draw_rect(arry, x, y, w, h, fill_value=0):
arry[y : y + h, x : x + w] = fill_value
return arry
notmask1 = ~mask1
notmask2 = ~mask2
return arry * (notmask1 + notmask2) + value * (mask1 + mask2)
def draw_arc(arry, cx, cy, r1, r2, a1, a2, fill_value=0):
else:
"""" draw circular arc """
if amini >= 180:
amini -= 360
amaxi -= 360
h, w = arry.shape[0:2]
mask = arcmask(w, h, cx, cy, r1, r2, a1, a2)
return np.where(
mask, fill_value, arry
) # where True in mask use fill_value else arry
c = np.arctan2(y - cy, x - cx) >= (amini * DEG2RAD)
d = np.arctan2(y - cy, x - cx) <= (amaxi * DEG2RAD)
mask = a * b * c * d
# ------ BUILD SPECIAL IMAGES ------------------
notmask = ~mask
return arry * notmask + mask * value
def test_image(w=800, h=600):
arry = np.ones((h, w))
arry = draw_rect(
arry, int(w * 0.1), int(h * 0.1), int(w * 0.1), int(h * 0.1), fill_value=0
)
arry = draw_rect(
arry, int(w * 0.5), int(h * 0.1), int(w * 0.15), int(h * 0.15), fill_value=50
)
arry = draw_rect(
arry, int(w * 0.1), int(h * 0.5), int(w * 0.2), int(h * 0.2), fill_value=100
)
arry = draw_rect(
arry, int(w * 0.5), int(h * 0.5), int(w * 0.25), int(h * 0.25), fill_value=150
)
# ------ BUILD SPECIAL IMAGES ------------------
return arry.astype("uint8")
def create_beam_images(
fdir,
nframes=100,
nframes=10,
w=800,
h=600,
amp=1000.,
centre=(0, 0),
centre=(None, None),
sigma=(100, 100),
noise=0.1,
):
......@@ -283,23 +270,28 @@ def create_beam_images(
- w: image width
- h: image height
- amp: maximum intensity of the Gaussian beam
- centre: position (cx, cy) of the Gaussian beam (centred on image center by default)
- centre: position (cx, cy) of the Gaussian beam (image center by default)
- sigma: sigma values (sx, sy) of the Gaussian beam
- noise: noise level in perecentage of the Gaussian amplitude (%)
"""
amp = min(amp, 2 ** 31)
# centre by default
cx, cy = centre
if cx is None:
cx = w / 2
if cy is None:
cy = h / 2
# generate random Gaussian parameters (centred + jitter)
A = np.random.default_rng().normal(amp, 5, size=nframes)
sx = np.random.default_rng().normal(sigma[0], 1, size=nframes)
sy = np.random.default_rng().normal(sigma[1], 1, size=nframes)
x0 = np.random.default_rng().normal(centre[0], 2, size=nframes)
y0 = np.random.default_rng().normal(centre[1], 2, size=nframes)
x0 = np.random.default_rng().normal(cx, 2, size=nframes)
y0 = np.random.default_rng().normal(cy, 2, size=nframes)
# generate beam images
frames = []
for i in range(nframes):
bg = np.random.default_rng().normal(amp * noise, 10, size=(h, w))
arry = gauss2d(w, h, A[i], sx[i], sy[i], x0[i], y0[i]) + bg
......@@ -311,28 +303,51 @@ def create_beam_images(
# convert to uint32
arry = arry.astype("uint32")
# save as image file (edf)
fpath = f"{fdir}/frame_{i:04d}.edf"
array_to_file(arry, fpath)
frames.append(arry)
return frames
def create_ring_image(fpath, w=800, h=600, cx=0, cy=0, r1=100, r2=120, a1=0, a2=180):
"""" create arc """
# ----- PLOT IMAGE ----------------------
cx = w / 2 + cx
cy = h / 2 + cy
x = np.linspace(0, w - 1, w)
y = np.linspace(0, h - 1, h)
x, y = np.meshgrid(x, y)
def get_image_display(interactive=True, dtmin=0.001, defsize=(800, 600)):
""" Plot 2D array as an image (static or live update) """
import matplotlib.pyplot as plt
class Display:
def __init__(self, interactive=True, dtmin=0.001, defsize=(800, 600)):
self._interactive = interactive
self._dtmin = dtmin
if interactive:
plt.ion()
else:
plt.ioff()
self.plot = plt.imshow(np.zeros((defsize[1], defsize[0])))
plt.pause(self._dtmin)
def __del__(self):
plt.close()
plt.ioff()
def show(self, arry):
try:
plt.cla() # clear axes
# plt.clf() # clear figure
except Exception:
pass
self.plot = plt.imshow(arry)
plt.pause(self._dtmin)
a = (x - cx) ** 2 + (y - cy) ** 2 >= r2 ** 2
b = (x - cx) ** 2 + (y - cy) ** 2 <= r1 ** 2
c = y >= h / 2
r = (a + b + c) * 1
r = r.astype("uint32")
if not self._interactive:
plt.show()
array_to_file(r, fpath)
def close(self):
plt.close()
plt.ioff()
return r
return Display(interactive, dtmin, defsize)
This diff is collapsed.
......@@ -13,46 +13,7 @@ from bliss.controllers.lima.limatools import (
from bliss.data.lima_image import image_from_server
from bliss.shell.formatters.table import IncrementalTable
# ------ Utility function to PLOT 2D ARRAY AS AN IMAGE (LIVE PREVIEW) --------
def get_image_display(interactive=True, dtmin=0.001, defsize=(800, 600)):
import matplotlib.pyplot as plt
class Display:
def __init__(self, interactive=True, dtmin=0.001, defsize=(800, 600)):
self._interactive = interactive
self._dtmin = dtmin
if interactive:
plt.ion()
else:
plt.ioff()
self.plot = plt.imshow(numpy.zeros((defsize[1], defsize[0])))
plt.pause(self._dtmin)
def __del__(self):
plt.close()
plt.ioff()
def show(self, arry):
try:
plt.cla() # clear axes
# plt.clf() # clear figure
except Exception:
pass
self.plot = plt.imshow(arry)
if self._interactive:
plt.pause(self._dtmin)
else:
plt.show()
def close(self):
plt.close()
plt.ioff()
return Display(interactive, dtmin, defsize)
from bliss.common.image_tools import get_image_display
# --- Notes about rect vs roi ------------
......
......@@ -1153,8 +1153,8 @@ def test_roi_collection(default_session, lima_simulator, tmp_path):
# === check saved image files ======
for idx in range(frames):
fpath = os.path.join(imgdir, f"test_rois_{idx:04d}.edf")
mode, size, img = file_to_array(fpath)
print("img info", mode, size, img.dtype)
img = file_to_array(fpath)
print("img info", img.shape, img.dtype)
# img.dtype = defdtype
plt.imshow(img)
plt.show()
......
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import os
from bliss.common.scans.simulation import simu_l2scan, simu_mesh
def test_zap(session, images_directory):
img_path = os.path.join(str(images_directory), "bliss_logo.png")
fast_motor = session.config.get("roby")
slow_motor = session.config.get("robz")
cnt = session.config.get("diode")
simu_l2scan(
fast_motor,
0,
3,
3,
slow_motor,
0,
1,
1,
0.01,
cnt,
backnforth=False,
scale=0.1,
imshow=False,
simdatapath=img_path,
)
def test_simul_mesh(session, images_directory):
img_path = os.path.join(str(images_directory), "bliss_logo.png")
roby = session.config.get("roby")
robz = session.config.get("robz")
diode = session.config.get("diode")
simu_mesh(roby, robz, diode, size=(3, 3), imshow=False, simdatapath=img_path)
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