Files
camserver_sf/configuration/user_scripts/cams_fb.py
T
2026-03-27 10:41:57 +01:00

128 lines
4.0 KiB
Python

from logging import getLogger
from cam_server.pipeline.data_processing import functions, processor
from cam_server.utils import create_thread_pvs, epics_lock
from collections import deque
from threading import Thread
import time
import numpy as np
_logger = getLogger(__name__)
# per-camera state (so multiple camera_name streams don't fight each other)
_states = {}
# channels coming out of processor.process_image()
CHANNELS = [
"intensity",
"x_center_of_mass", "x_fwhm", "x_rms",
"x_fit_amplitude", "x_fit_mean", "x_fit_offset", "x_fit_standard_deviation",
"x_profile",
"y_center_of_mass", "y_fwhm", "y_rms",
"y_fit_amplitude", "y_fit_mean", "y_fit_offset", "y_fit_standard_deviation",
"y_profile",
]
# which of the above are arrays (these should be waveform records in your IOC)
ARRAY_SUFFIXES = {"x_profile", "y_profile"}
def _pv_safe(val, pvname, array_pvs):
"""Match your example: ensure arrays are 1D float64, scalars are floats."""
if val is None:
return None
if pvname in array_pvs:
if isinstance(val, np.ndarray):
if val.ndim == 1:
return val.astype(np.float64, copy=False)
if val.ndim == 0:
return np.array([val.item()], dtype=np.float64)
raise ValueError(f"{pvname}: expected 1D array, got shape {val.shape}")
if isinstance(val, (list, tuple)):
return np.array(val, dtype=np.float64)
if isinstance(val, (float, int, np.generic)):
return np.array([float(val)], dtype=np.float64)
raise TypeError(f"{pvname}: expected array, got {type(val)}")
# scalar PV
if isinstance(val, np.ndarray):
if val.size == 1:
return float(val.item())
raise ValueError(f"{pvname}: expected scalar, got array of size {val.size}")
if isinstance(val, (np.generic, float, int)):
return float(val)
raise TypeError(f"{pvname}: expected scalar, got {type(val)}")
def update_PVs(buffer, *pv_names):
pvs = create_thread_pvs(list(pv_names))
while True:
time.sleep(0.01)
try:
rec = buffer.popleft()
except IndexError:
continue
for pv, val in zip(pvs, rec):
if pv and pv.connected and (val is not None):
try:
pv.put(val)
except Exception as e:
_logger.error(f"Error writing to {pv.pvname}: {e}")
def _initialize_camera_state(camera_name: str):
buffer = deque(maxlen=10)
pv_names = [f"{camera_name}:{c}" for c in CHANNELS]
array_pvs = {f"{camera_name}:{c}" for c in ARRAY_SUFFIXES}
thread = Thread(target=update_PVs, args=(buffer, *pv_names), daemon=True)
thread.start()
return {
"buffer": buffer,
"pv_names": pv_names,
"array_pvs": array_pvs,
"sent_pid": -1,
}
def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata):
"""
Calls processor.process_image(...), then pushes results to EPICS PVs:
<camera_name>:<suffix>
No dict returned.
"""
camera = parameters["camera_name"]
roi = parameters["roi"]
invert_image = parameters["invert_image"]
st = _states.get(camera)
if st is None:
st = _states[camera] = _initialize_camera_state(camera)
if roi:
image = image[roi[0]:roi[1],roi[2]:roi[3]]
if invert_image:
image = 1/image
y_axis_len, x_axis_len = np.shape(image)
x_axis = np.arange(x_axis_len)
y_axis = np.arange(y_axis_len)
r = processor.process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata)
if not epics_lock.acquire(False):
return
try:
if pulse_id <= st["sent_pid"]:
return
st["sent_pid"] = pulse_id
entry = []
for suffix in CHANNELS:
pvname = f"{camera}:{suffix}"
entry.append(_pv_safe(r.get(suffix), pvname, st["array_pvs"]))
st["buffer"].append(tuple(entry))
finally:
epics_lock.release()