GUI: only initialise workflow SSE client if test flag is True. WIP
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-12-11T16:18:13.726898285Z",
|
||||
"start_time": "2025-12-11T16:18:13.626609939Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"from aaredaqlib.beamline import MXBeamline\n",
|
||||
"from mxlibs3.non_standard import NonStandard"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "ModuleNotFoundError",
|
||||
"evalue": "No module named 'aaredaqlib'",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
|
||||
"\u001B[0;31mModuleNotFoundError\u001B[0m Traceback (most recent call last)",
|
||||
"Cell \u001B[0;32mIn[1], line 1\u001B[0m\n\u001B[0;32m----> 1\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21;01maaredaqlib\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mbeamline\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m MXBeamline\n\u001B[1;32m 2\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21;01mmxlibs3\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mnon_standard\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m NonStandard\n",
|
||||
"\u001B[0;31mModuleNotFoundError\u001B[0m: No module named 'aaredaqlib'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 1
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"source": "BEAMLINE=MXBeamline.X10SA",
|
||||
"id": "7f9204607618d224",
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"gmx = NonStandard(\n",
|
||||
" name=\"GMX\",\n",
|
||||
" setpv=f\"{BEAMLINE}-ES-DF1:TRX1.VAL\",\n",
|
||||
" getpv=f\"{BEAMLINE}-ES-DF1:TRX.RBV\",\n",
|
||||
" speed=(f\"{BEAMLINE}-ES-DF1:TRX.VELO\", f\"{BEAMLINE}-ES-DF1:TRX.VELO\"),\n",
|
||||
" )\n"
|
||||
],
|
||||
"id": "8d0451d661f24051",
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"execution_count": null,
|
||||
"source": "",
|
||||
"id": "343959c075c154b0"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "venv_kernel",
|
||||
"language": "python",
|
||||
"name": "venv_kernel"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import cv2
|
||||
#%%
|
||||
bkg_old = cv2.imread("/home/leonarski_f/aaredaq/daq/src/aaredaq/alc_test_images/bkg280_50_50_old.jpg")
|
||||
bkg_new = cv2.imread("/home/leonarski_f/aaredaq/daq/src/aaredaq/alc_test_images/bkg280_50_50.jpg")
|
||||
grey_old = cv2.cvtColor(bkg_old, cv2.COLOR_RGB2GRAY)
|
||||
grey_new = cv2.cvtColor(bkg_new, cv2.COLOR_RGB2GRAY)
|
||||
diff_image = cv2.absdiff(grey_old, grey_new)
|
||||
_, thresh_diff = cv2.threshold(diff_image, 1, 255, cv2.THRESH_BINARY)
|
||||
|
||||
# Display the results
|
||||
cv2.imshow('Original Image 1', bkg_old)
|
||||
cv2.imshow('Original Image 2', bkg_new)
|
||||
cv2.imshow('Grayscale 1', grey_old)
|
||||
cv2.imshow('Grayscale 2', grey_new)
|
||||
cv2.imshow('Difference Image', diff_image)
|
||||
cv2.imshow('Thresholded Difference', thresh_diff)
|
||||
|
||||
# Save the difference image
|
||||
cv2.imwrite('/home/leonarski_f/aaredaq/daq/src/aaredaq/alc_test_images/bkg_difference_image.jpg', diff_image)
|
||||
cv2.imwrite('/home/leonarski_f/aaredaq/daq/src/aaredaq/alc_test_images/bkg_thresholded_difference.jpg', thresh_diff)
|
||||
|
||||
# Wait for a key press and close all windows
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
from aaredaqlib.beamline import MXBeamline
|
||||
from mxlibs3.non_standard import NonStandard
|
||||
|
||||
BEAMLINE = MXBeamline.X10SA.value.upper()
|
||||
# print(BEAMLINE)
|
||||
# gmx = NonStandard(
|
||||
# name="GMX",
|
||||
# setpv=f"{BEAMLINE}-ES-DF1:TRX1.VAL",
|
||||
# getpv=f"{BEAMLINE}-ES-DF1:TRX1.RBV",
|
||||
# speed=(f"{BEAMLINE}-ES-DF1:TRX1.VELO", f"{BEAMLINE}-ES-DF1:TRX1.VELO"),
|
||||
# )
|
||||
#
|
||||
# print(gmx.readback)
|
||||
# gmx.move(5,True)
|
||||
# print(gmx.readback)
|
||||
|
||||
collimator = NonStandard(
|
||||
name="Collimator",
|
||||
setpv=f"{BEAMLINE}-ES-COL:TRY.VAL",
|
||||
getpv=f"{BEAMLINE}-ES-COL:TRY.RBV",
|
||||
tolerance=0.01,
|
||||
predefs={
|
||||
"parking": 1.0,
|
||||
"measure": 41.2,
|
||||
"down": 20.0}
|
||||
)
|
||||
|
||||
collimator.move("down")
|
||||
@@ -0,0 +1,52 @@
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
from bec_lib.user_macros import UserMacros
|
||||
import sys
|
||||
|
||||
sys.path.append("/sls/MX/applications/test_scripts/bec_tes")
|
||||
|
||||
service_config = ServiceConfig(redis={"host": "x06da-bec-001", "port": 6379})
|
||||
client = BECClient(service_config, name="Martins-Custom-Client")
|
||||
client.start()
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/calculator.py")
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/pxiii_parameters.py")
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/pxiii_energy.py")
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/mx_methods.py")
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/mx_basics.py")
|
||||
client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/energy_check.py")
|
||||
#client.macros.load_user_macro("/sls/MX/applications/test_scripts/bec_tes/y")
|
||||
dev=client.device_manager.devices
|
||||
macros = client.macros
|
||||
scans = client.scans
|
||||
|
||||
try:
|
||||
energy_ev = validate_energy(13)
|
||||
current_energy = get_current_energy()
|
||||
energy_diff = calculate_energy_difference(current_energy, energy_ev)
|
||||
dccm_pos = get_dccm_motors_positions(energy_ev)
|
||||
print(dev.dccm_theta1)
|
||||
print(dev.dccm_theta2)
|
||||
print(
|
||||
f"Moving DCCM theta1: {dccm_pos['theta1_angle']: .5g} deg, theta2: {dccm_pos['theta2_angle']: .5g} deg, "
|
||||
# f"DCM pitch: {dcm_pos['dcm_pitch']: .5g} mrad, "
|
||||
)
|
||||
theta = scans.umv(dev.dccm_theta1, dccm_pos["theta1_angle"], relative=False)
|
||||
theta.wait()
|
||||
theta_2 = scans.umv(dev.dccm_theta2, dccm_pos["theta2_angle"], relative=False)
|
||||
theta_2.wait()
|
||||
set_mirror_stripe(energy_ev)
|
||||
print(
|
||||
f"Energy difference: {energy_diff: .5g} eV, current energy: {current_energy: .5g} eV"
|
||||
)
|
||||
mono_pitch_scan(scans, plot=False)
|
||||
#bl_energy(energy_ev=13, scans=scans, plot=False)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
|
||||
# print(dev.det_z.read())
|
||||
# status = scans.mv(dev.det_z, 770.0, relative=False)
|
||||
# status.wait()
|
||||
# print(dev.det_z.read())
|
||||
client.shutdown()
|
||||
@@ -0,0 +1,447 @@
|
||||
import json
|
||||
import cv2
|
||||
import numpy as np
|
||||
import zmq
|
||||
import time
|
||||
import threading
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class ImageStatsReceiver:
|
||||
def __init__(self, zmq_url: str):
|
||||
"""Initialize ZeroMQ receiver for image statistics calculation"""
|
||||
context = zmq.Context()
|
||||
self.__socket = context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
||||
self.__socket.setsockopt(zmq.LINGER, 0) # Don't linger on close
|
||||
|
||||
try:
|
||||
self.__socket.connect(zmq_url)
|
||||
print(f"Connected to ZeroMQ socket: {zmq_url}")
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to {zmq_url}: {e}")
|
||||
raise
|
||||
|
||||
self.running = False
|
||||
self.latest_stats = None
|
||||
self.stats_lock = threading.Lock()
|
||||
self.message_count = 0
|
||||
self.connection_attempts = 0
|
||||
self.last_message_time = None
|
||||
self.connection_attempts = 0
|
||||
self.last_message_time = None
|
||||
|
||||
def calculate_projections(self, image: np.ndarray) -> Dict[str, Any]:
|
||||
"""Calculate X and Y projections of the image"""
|
||||
# Convert to grayscale if color image
|
||||
if len(image.shape) == 3:
|
||||
gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
||||
else:
|
||||
gray_image = image.copy()
|
||||
|
||||
# Calculate projections (sum along each axis)
|
||||
x_projection = np.mean(gray_image, axis=0) # Sum along Y axis -> X profile
|
||||
y_projection = np.mean(gray_image, axis=1) # Sum along X axis -> Y profile
|
||||
|
||||
# Also calculate max projections (useful for finding peaks)
|
||||
x_projection_max = np.max(gray_image, axis=0)
|
||||
y_projection_max = np.max(gray_image, axis=1)
|
||||
|
||||
# Find peak positions and values
|
||||
x_peak_idx = np.argmax(x_projection)
|
||||
y_peak_idx = np.argmax(y_projection)
|
||||
x_peak_value = x_projection[x_peak_idx]
|
||||
y_peak_value = y_projection[y_peak_idx]
|
||||
|
||||
# Calculate centroid positions (intensity-weighted center)
|
||||
x_coords = np.arange(len(x_projection))
|
||||
y_coords = np.arange(len(y_projection))
|
||||
|
||||
x_centroid = np.sum(x_coords * x_projection) / np.sum(x_projection) if np.sum(x_projection) > 0 else len(
|
||||
x_projection) / 2
|
||||
y_centroid = np.sum(y_coords * y_projection) / np.sum(y_projection) if np.sum(y_projection) > 0 else len(
|
||||
y_projection) / 2
|
||||
|
||||
# Calculate FWHM (Full Width at Half Maximum) approximation
|
||||
def calculate_fwhm(profile):
|
||||
peak_val = np.max(profile)
|
||||
half_max = peak_val / 2
|
||||
indices = np.where(profile >= half_max)[0]
|
||||
if len(indices) > 0:
|
||||
return indices[-1] - indices[0]
|
||||
return 0
|
||||
|
||||
x_fwhm = calculate_fwhm(x_projection)
|
||||
y_fwhm = calculate_fwhm(y_projection)
|
||||
|
||||
return {
|
||||
'x_projection': x_projection.tolist(),
|
||||
'y_projection': y_projection.tolist(),
|
||||
'x_projection_max': x_projection_max.tolist(),
|
||||
'y_projection_max': y_projection_max.tolist(),
|
||||
'x_peak_position': int(x_peak_idx),
|
||||
'y_peak_position': int(y_peak_idx),
|
||||
'x_peak_value': float(x_peak_value),
|
||||
'y_peak_value': float(y_peak_value),
|
||||
'x_centroid': float(x_centroid),
|
||||
'y_centroid': float(y_centroid),
|
||||
'x_fwhm': float(x_fwhm),
|
||||
'y_fwhm': float(y_fwhm),
|
||||
'x_coords': list(range(len(x_projection))),
|
||||
'y_coords': list(range(len(y_projection)))
|
||||
}
|
||||
|
||||
def calculate_radial_integration(self, image: np.ndarray, num_bins: int = 50) -> Dict[str, Any]:
|
||||
"""Calculate radial integration of the image"""
|
||||
# Convert to grayscale if color image
|
||||
if len(image.shape) == 3:
|
||||
gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
||||
else:
|
||||
gray_image = image.copy()
|
||||
|
||||
# Get image center
|
||||
center_y, center_x = np.array(gray_image.shape) // 2
|
||||
|
||||
# Create coordinate grids
|
||||
y_coords, x_coords = np.ogrid[:gray_image.shape[0], :gray_image.shape[1]]
|
||||
|
||||
# Calculate distance from center for each pixel
|
||||
distances = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2)
|
||||
|
||||
# Maximum possible radius (to corner of image)
|
||||
max_radius = np.sqrt((gray_image.shape[0] / 2) ** 2 + (gray_image.shape[1] / 2) ** 2)
|
||||
|
||||
# Create radial bins
|
||||
r_bins = np.linspace(0, max_radius, num_bins + 1)
|
||||
r_centers = (r_bins[:-1] + r_bins[1:]) / 2
|
||||
|
||||
# Calculate radial profile
|
||||
radial_profile = []
|
||||
radial_std = []
|
||||
pixel_counts = []
|
||||
|
||||
for i in range(num_bins):
|
||||
# Create mask for current radial bin
|
||||
mask = (distances >= r_bins[i]) & (distances < r_bins[i + 1])
|
||||
|
||||
if np.any(mask):
|
||||
# Get pixel values in this radial bin
|
||||
pixel_values = gray_image[mask]
|
||||
radial_profile.append(float(np.mean(pixel_values)))
|
||||
radial_std.append(float(np.std(pixel_values)))
|
||||
pixel_counts.append(int(np.sum(mask)))
|
||||
else:
|
||||
radial_profile.append(0.0)
|
||||
radial_std.append(0.0)
|
||||
pixel_counts.append(0)
|
||||
|
||||
return {
|
||||
'r_centers': r_centers.tolist(),
|
||||
'radial_profile': radial_profile,
|
||||
'radial_std': radial_std,
|
||||
'pixel_counts': pixel_counts,
|
||||
'max_radius': float(max_radius),
|
||||
'center': [int(center_x), int(center_y)],
|
||||
'num_bins': num_bins
|
||||
}
|
||||
|
||||
def calculate_image_stats(self, image: np.ndarray) -> Dict[str, Any]:
|
||||
"""Calculate comprehensive statistics for an image"""
|
||||
image_float = image.astype(np.float64)
|
||||
|
||||
stats = {
|
||||
'timestamp': time.time(),
|
||||
'shape': image.shape,
|
||||
'dtype': str(image.dtype),
|
||||
'mean': float(np.mean(image_float)),
|
||||
'std': float(np.std(image_float)),
|
||||
'median': float(np.median(image_float)),
|
||||
'min': float(np.min(image_float)),
|
||||
'max': float(np.max(image_float)),
|
||||
'message_count': self.message_count
|
||||
}
|
||||
|
||||
# Calculate per-channel stats if color image
|
||||
if len(image.shape) == 3 and image.shape[2] > 1:
|
||||
for channel in range(image.shape[2]):
|
||||
channel_data = image_float[:, :, channel]
|
||||
stats[f'mean_ch{channel}'] = float(np.mean(channel_data))
|
||||
stats[f'std_ch{channel}'] = float(np.std(channel_data))
|
||||
stats[f'median_ch{channel}'] = float(np.median(channel_data))
|
||||
|
||||
radial_data = self.calculate_radial_integration(image)
|
||||
stats['radial'] = radial_data
|
||||
|
||||
#projection_data = self.calculate_projections(image)
|
||||
#stats['projections'] = projection_data
|
||||
|
||||
return stats
|
||||
|
||||
def process_message(self):
|
||||
"""Process a single ZeroMQ message"""
|
||||
try:
|
||||
r = self.__socket.recv_multipart(zmq.NOBLOCK)
|
||||
if len(r) != 2:
|
||||
return None
|
||||
|
||||
meta, data = r
|
||||
header = json.loads(meta)
|
||||
header_shape = header["shape"]
|
||||
self.message_count += 1
|
||||
|
||||
if header["type"] == "uint8" and len(header_shape) == 2:
|
||||
# Bayer image
|
||||
bayer_image = np.frombuffer(data, dtype=np.uint8)
|
||||
bayer_image = bayer_image.reshape(header_shape)
|
||||
|
||||
# Convert to RGB for main stats
|
||||
rgb_image = cv2.cvtColor(bayer_image, cv2.COLOR_BAYER_GB2RGB)
|
||||
rgb_image = rgb_image[:, ::-1, :].copy()
|
||||
|
||||
stats = self.calculate_image_stats(rgb_image)
|
||||
stats['image_type'] = 'rgb_from_bayer'
|
||||
|
||||
with self.stats_lock:
|
||||
self.latest_stats = stats
|
||||
|
||||
return stats
|
||||
|
||||
elif header["type"] == "uint8" and len(header_shape) == 3:
|
||||
# RGB image
|
||||
rgb_image = np.frombuffer(data, dtype=np.uint8)
|
||||
rgb_image = rgb_image.reshape(header_shape)
|
||||
|
||||
stats = self.calculate_image_stats(rgb_image)
|
||||
stats['image_type'] = 'rgb_direct'
|
||||
|
||||
with self.stats_lock:
|
||||
self.latest_stats = stats
|
||||
|
||||
return stats
|
||||
|
||||
except zmq.Again:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error processing ZMQ message: {e}")
|
||||
return None
|
||||
|
||||
def receiver_thread(self):
|
||||
"""Background thread for receiving messages"""
|
||||
while self.running:
|
||||
self.process_message()
|
||||
time.sleep(0.001) # Small sleep to prevent busy waiting
|
||||
|
||||
def print_stats_thread(self):
|
||||
"""Background thread for printing stats every second"""
|
||||
while self.running:
|
||||
time.sleep(5.0) # Print once per second
|
||||
|
||||
with self.stats_lock:
|
||||
if self.latest_stats:
|
||||
self.print_formatted_stats(self.latest_stats)
|
||||
else:
|
||||
print(f"[{time.strftime('%H:%M:%S')}] Waiting for image data...")
|
||||
|
||||
def print_formatted_stats(self, stats: Dict[str, Any]):
|
||||
"""Print formatted statistics in a compact terminal format"""
|
||||
timestamp = time.strftime('%H:%M:%S', time.localtime(stats['timestamp']))
|
||||
|
||||
# Compact one-line format
|
||||
print(f"[{timestamp}] #{stats['message_count']:4d} | "
|
||||
f"Shape: {stats['shape']} | "
|
||||
f"Mean: {stats['mean']:6.1f} | "
|
||||
f"Std: {stats['std']:6.1f} | "
|
||||
f"Median: {stats['median']:6.1f} | "
|
||||
f"Range: [{stats['min']:3.0f}-{stats['max']:3.0f}]",
|
||||
flush=True)
|
||||
|
||||
if 'radial' in stats:
|
||||
radial = stats['radial']
|
||||
center_intensity = radial['radial_profile'][0] if radial['radial_profile'] else 0
|
||||
edge_intensity = radial['radial_profile'][-1] if radial['radial_profile'] else 0
|
||||
peak_radius_idx = np.argmax(radial['radial_profile']) if radial['radial_profile'] else 0
|
||||
peak_radius = radial['r_centers'][peak_radius_idx] if radial['r_centers'] else 0
|
||||
|
||||
print(f"{'':21} Radial: Center={center_intensity:.1f} | "
|
||||
f"Edge={edge_intensity:.1f} | "
|
||||
f"Peak@r={peak_radius:.1f} | "
|
||||
f"Center=({radial['center'][0]},{radial['center'][1]})",
|
||||
flush=True)
|
||||
|
||||
# Print projection summary
|
||||
if 'projections' in stats:
|
||||
proj = stats['projections']
|
||||
print(f"{'':21} X-Profile: Peak@{proj['x_peak_position']}({proj['x_peak_value']:.1f}) | "
|
||||
f"Centroid={proj['x_centroid']:.1f} | FWHM={proj['x_fwhm']:.1f}",
|
||||
flush=True)
|
||||
print(f"{'':21} Y-Profile: Peak@{proj['y_peak_position']}({proj['y_peak_value']:.1f}) | "
|
||||
f"Centroid={proj['y_centroid']:.1f} | FWHM={proj['y_fwhm']:.1f}",
|
||||
flush=True)
|
||||
|
||||
# Optional: Print per-channel stats if available
|
||||
if any(k.startswith('mean_ch') for k in stats.keys()):
|
||||
channels = []
|
||||
i = 0
|
||||
while f'mean_ch{i}' in stats:
|
||||
channels.append(f"Ch{i}({stats[f'mean_ch{i}']:.1f})")
|
||||
i += 1
|
||||
if channels:
|
||||
print(f"{'':21} Channels: {' '.join(channels)}", flush=True)
|
||||
|
||||
def get_radial_profile_summary(self):
|
||||
"""Get a summary of the current radial profile"""
|
||||
with self.stats_lock:
|
||||
if self.latest_stats and 'radial' in self.latest_stats:
|
||||
radial = self.latest_stats['radial']
|
||||
return {
|
||||
'r_centers': radial['r_centers'],
|
||||
'radial_profile': radial['radial_profile'],
|
||||
'center_intensity': radial['radial_profile'][0] if radial['radial_profile'] else 0,
|
||||
'edge_intensity': radial['radial_profile'][-1] if radial['radial_profile'] else 0,
|
||||
'max_intensity_radius': radial['r_centers'][np.argmax(radial['radial_profile'])] if radial[
|
||||
'radial_profile'] else 0,
|
||||
'max_intensity_value': max(radial['radial_profile']) if radial['radial_profile'] else 0
|
||||
}
|
||||
return None
|
||||
|
||||
def save_radial_profile_to_file(self, filename: str = None):
|
||||
"""Save the current radial profile to a file"""
|
||||
if filename is None:
|
||||
filename = f"radial_profile_{int(time.time())}.txt"
|
||||
|
||||
with self.stats_lock:
|
||||
if self.latest_stats and 'radial' in self.latest_stats:
|
||||
radial = self.latest_stats['radial']
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.write("# Radial Integration Profile\n")
|
||||
f.write(f"# Timestamp: {time.ctime(self.latest_stats['timestamp'])}\n")
|
||||
f.write(f"# Image shape: {self.latest_stats['shape']}\n")
|
||||
f.write(f"# Center: {radial['center']}\n")
|
||||
f.write("# Radius(pixels)\tMean_Intensity\tStd_Intensity\tPixel_Count\n")
|
||||
|
||||
for i in range(len(radial['r_centers'])):
|
||||
f.write(f"{radial['r_centers'][i]:.2f}\t"
|
||||
f"{radial['radial_profile'][i]:.2f}\t"
|
||||
f"{radial['radial_std'][i]:.2f}\t"
|
||||
f"{radial['pixel_counts'][i]}\n")
|
||||
|
||||
print(f"Radial profile saved to {filename}")
|
||||
return filename
|
||||
|
||||
print("No radial profile data available")
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
"""Start the background threads"""
|
||||
self.running = True
|
||||
|
||||
# Start receiver thread
|
||||
self.receiver_thread_obj = threading.Thread(target=self.receiver_thread, daemon=True)
|
||||
self.receiver_thread_obj.start()
|
||||
|
||||
# Start stats printing thread
|
||||
self.print_thread_obj = threading.Thread(target=self.print_stats_thread, daemon=True)
|
||||
self.print_thread_obj.start()
|
||||
|
||||
print(f"Image stats receiver started - printing every 1 second")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the receiver and all threads"""
|
||||
self.running = False
|
||||
if self.__socket:
|
||||
self.__socket.close()
|
||||
print("Image stats receiver stopped")
|
||||
|
||||
|
||||
# Global stats receiver instance
|
||||
stats_receiver = None
|
||||
|
||||
|
||||
def start_image_stats_receiver(zmq_url: str = "tcp://localhost:5555"):
|
||||
"""Start the image stats receiver in background"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver is None or not stats_receiver.running:
|
||||
try:
|
||||
stats_receiver = ImageStatsReceiver(zmq_url)
|
||||
stats_receiver.start()
|
||||
print(f"Started image statistics monitoring on {zmq_url}")
|
||||
except Exception as e:
|
||||
print(f"Failed to start image stats receiver: {e}")
|
||||
stats_receiver = None
|
||||
|
||||
|
||||
def stop_image_stats_receiver():
|
||||
"""Stop the image stats receiver"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver and stats_receiver.running:
|
||||
stats_receiver.stop()
|
||||
stats_receiver = None
|
||||
|
||||
|
||||
def get_latest_image_stats():
|
||||
"""Get the latest image statistics"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver and stats_receiver.latest_stats:
|
||||
with stats_receiver.stats_lock:
|
||||
return stats_receiver.latest_stats.copy()
|
||||
return None
|
||||
|
||||
|
||||
def get_latest_image_stats():
|
||||
"""Get the latest image statistics including radial profile"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver and stats_receiver.latest_stats:
|
||||
with stats_receiver.stats_lock:
|
||||
return stats_receiver.latest_stats.copy()
|
||||
return None
|
||||
|
||||
|
||||
def get_radial_profile():
|
||||
"""Get just the radial profile data"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver:
|
||||
return stats_receiver.get_radial_profile_summary()
|
||||
return None
|
||||
|
||||
|
||||
def save_current_radial_profile(filename: str = None):
|
||||
"""Save the current radial profile to a file"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver:
|
||||
return stats_receiver.save_radial_profile_to_file(filename)
|
||||
return None
|
||||
|
||||
# # Auto-start when daq.py is imported/run
|
||||
# if __name__ == "__main__":
|
||||
# # If running daq.py directly
|
||||
# start_image_stats_receiver()
|
||||
#
|
||||
# try:
|
||||
# # Your existing daq.py code continues here
|
||||
# print("DAQ system running with image statistics monitoring...")
|
||||
#
|
||||
# # Keep the program running
|
||||
# while True:
|
||||
# time.sleep(1)
|
||||
#
|
||||
# # Optional: Access latest stats in your main code
|
||||
# latest = get_latest_image_stats()
|
||||
# if latest and latest['message_count'] % 10 == 0: # Every 10th message
|
||||
# print(f"Main thread sees: Mean={latest['mean']:.1f}")
|
||||
#
|
||||
# except KeyboardInterrupt:
|
||||
# print("\nShutting down...")
|
||||
# finally:
|
||||
# stop_image_stats_receiver()
|
||||
#
|
||||
# else:
|
||||
# # If daq.py is imported as a module, auto-start the receiver
|
||||
# start_image_stats_receiver()
|
||||
@@ -0,0 +1,4 @@
|
||||
from aaredaq.config import BeamlineConfig
|
||||
from aaredaqlib.beamline import MXBeamline
|
||||
c = BeamlineConfig(MXBeamline.X06DA)
|
||||
c.state_busy = False
|
||||
@@ -0,0 +1,292 @@
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class PShellClient:
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
self.sse_event_loop_thread = None
|
||||
self.subscribed_events = None
|
||||
self.event_callback = None
|
||||
|
||||
def _get_response(self, response, is_json=True):
|
||||
if response.status_code != 200:
|
||||
raise Exception(response.text)
|
||||
return json.loads(response.text) if is_json else response.text
|
||||
|
||||
def _get_binary_response(self, response):
|
||||
if response.status_code != 200:
|
||||
raise Exception(response.text)
|
||||
return response.raw.read()
|
||||
|
||||
def get_version(self):
|
||||
"""Return application version.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
String with application version.
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/version"), False)
|
||||
|
||||
def get_config(self):
|
||||
"""Return application configuration.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
Dictionary.
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/config"))
|
||||
|
||||
def get_state(self):
|
||||
"""Return application state.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
String: Invalid, Initializing,Ready, Paused, Busy, Disabled, Closing, Fault, Offline
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/state"))
|
||||
|
||||
def get_logs(self):
|
||||
"""Return application logs.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
List of logs.
|
||||
Format of each log: [date, time, origin, level, description]
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/logs"))
|
||||
|
||||
def get_history(self, index):
|
||||
"""Access console command history.
|
||||
|
||||
Args:
|
||||
index(int): Index of history entry (0 is the most recent)
|
||||
|
||||
Returns:
|
||||
History entry
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/history/" + str(index)), False)
|
||||
|
||||
def get_script(self, path):
|
||||
"""Return script.
|
||||
|
||||
Args:
|
||||
path(str): Script path (absolute or relative to script folder)
|
||||
|
||||
Returns:
|
||||
String with file contents.
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/script/" + str(path)), False)
|
||||
|
||||
def get_devices(self):
|
||||
"""Return global devices.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
List of devices.
|
||||
Format of each device record: [name, type, state, value, age]
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/devices"))
|
||||
|
||||
def abort(self, command_id=None):
|
||||
"""Abort execution of command
|
||||
|
||||
Args:
|
||||
command_id(optional, int): id of the command to be aborted.
|
||||
if None (default), aborts the foreground execution.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if command_id is None:
|
||||
requests.get(url=self.url + "/abort")
|
||||
else:
|
||||
return requests.get(url=self.url + "/abort/" + str(command_id))
|
||||
|
||||
def reinit(self):
|
||||
"""Reinitialize the software.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url + "/reinit")
|
||||
|
||||
def stop(self):
|
||||
"""Stop all devices implementing the 'Stoppable' interface.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url + "/stop")
|
||||
|
||||
def update(self):
|
||||
"""Update all global devices.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url + "/update")
|
||||
|
||||
def eval(self, statement):
|
||||
"""Evaluates a statement in the interpreter.
|
||||
If the statement finishes by '&', it is executed in background.
|
||||
Otherwise statement is executed in foreground (exclusive).
|
||||
|
||||
Args:
|
||||
statement(str): input statement
|
||||
|
||||
Returns:
|
||||
String containing the console return.
|
||||
If an exception is produces in the interpretor, it is re-thrown here.
|
||||
"""
|
||||
statement = quote(statement)
|
||||
return self._get_response(requests.get(url=self.url + "/eval/" + statement), False)
|
||||
|
||||
def run(self, script, pars=None, background=False):
|
||||
"""Executes script in the interpreter.
|
||||
|
||||
Args:
|
||||
script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted.
|
||||
pars(optional, list or dict): if a list is given, it sets sys.argv for the script.
|
||||
If a dict is given, it sets global variable for the script.
|
||||
background(optional, bool): if True script is executed in background.
|
||||
|
||||
Returns:
|
||||
Return value of the script.
|
||||
If an exception is produces in the interpretor, it is re-thrown here.
|
||||
"""
|
||||
return self._get_response(
|
||||
requests.put(
|
||||
url=self.url + "/run", json={"script": script, "pars": pars, "background": background, "async": False}
|
||||
)
|
||||
)
|
||||
|
||||
def start_eval(self, statement):
|
||||
"""Starts evaluation of a statement in the interpreter.
|
||||
If the statement finishes by '&', it is executed in background.
|
||||
Otherwise statement is executed in foreground (exclusive).
|
||||
|
||||
Args:
|
||||
statement(str): input statement
|
||||
|
||||
Returns:
|
||||
Command id (int), which is used to retrieve command execution status/result (get_result).
|
||||
"""
|
||||
statement = quote(statement)
|
||||
return int(self._get_response(requests.get(url=self.url + "/evalAsync/" + statement), False))
|
||||
|
||||
def start_run(self, script, pars=None, background=False):
|
||||
"""Starts execution of a script in the interpreter.
|
||||
|
||||
Args:
|
||||
script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted.
|
||||
pars(optional, list or dict): if a list is given, it sets sys.argv for the script.
|
||||
If a dict is given, it sets global variable for the script.
|
||||
background(optional, bool): if True script is executed in background.
|
||||
|
||||
Returns:
|
||||
Command id (int), which is used to retrieve command execution status/result (get_result).
|
||||
"""
|
||||
return int(
|
||||
self._get_response(
|
||||
requests.put(
|
||||
url=self.url + "/run",
|
||||
json={"script": script, "pars": pars, "background": background, "async": True},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_result(self, command_id=-1):
|
||||
"""Gets status/result of a command executed asynchronously (start_eval and start_run).
|
||||
|
||||
Args:
|
||||
command_id(optional, int): command id. If equals to -1 (default) return status/result of the foreground task.
|
||||
|
||||
Returns:
|
||||
Dictionary with the fields: 'id' (int): command id
|
||||
'status' (str): unlaunched, invalid, removed, running, aborted, failed or completed.
|
||||
'exception' (str): if status equals 'failed', holds exception string.
|
||||
'return' (obj): if status equals 'completed', holds return value of script (start_run)
|
||||
or console return (start_eval)
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/result/" + str(command_id)))
|
||||
|
||||
def help(self, input="<builtins>"):
|
||||
"""Returns help or auto-completion strings.
|
||||
|
||||
Args:
|
||||
input(optional, str): - ":" for control commands
|
||||
- "<builtins>" for builtin functions
|
||||
- "devices" for device names
|
||||
- builtin function name for function help
|
||||
- else contains entry for auto-completion
|
||||
|
||||
Returns:
|
||||
List
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url + "/autocompletion/" + input))
|
||||
|
||||
def get_contents(self, path=None):
|
||||
"""Returns contents of data path.
|
||||
|
||||
Args:
|
||||
path(optional, str): Path to data relative to data home path.
|
||||
- Folder
|
||||
- File
|
||||
- File (data root) | internal path
|
||||
- internal path (on currently open data root)
|
||||
|
||||
Returns:
|
||||
List of contents
|
||||
|
||||
"""
|
||||
return self._get_response(
|
||||
requests.get(url=self.url + "/contents" + ("" if path is None else ("/" + path))), False
|
||||
)
|
||||
|
||||
def get_data(self, path, type="txt"):
|
||||
"""Returns data on a given path.
|
||||
|
||||
Args:
|
||||
path(str): Path to data relative to data home path.
|
||||
- File (data root) | internal path
|
||||
- internal path (on currently open data root)
|
||||
type(optional, str): txt, "json", "bin", "bs"
|
||||
|
||||
Returns:
|
||||
Data accordind to selected format/.
|
||||
|
||||
"""
|
||||
if type == "json":
|
||||
return self._get_response(requests.get(url=self.url + "/data-json/" + path), True)
|
||||
elif type == "bin":
|
||||
return self._get_binary_response(requests.get(url=self.url + "/data-bin/" + path, stream=True))
|
||||
|
||||
return self._get_response(requests.get(url=self.url + "/data" + ("" if path is None else ("/" + path))), False)
|
||||
|
||||
def print_logs(self):
|
||||
for log_line in self.get_logs():
|
||||
print("%s %s %-20s %-8s %s" % tuple(log_line))
|
||||
|
||||
def print_devices(self):
|
||||
for log_line in self.get_devices():
|
||||
print("%-16s %-32s %-10s %-32s %s" % tuple(log_line))
|
||||
@@ -0,0 +1,128 @@
|
||||
# python
|
||||
import sys
|
||||
import argparse
|
||||
from typing import Any, Dict, Tuple
|
||||
import yaml
|
||||
import redis
|
||||
|
||||
|
||||
def decode_bulk(value: Any):
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return repr(value)
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
return type(value)(decode_bulk(v) for v in value)
|
||||
if isinstance(value, dict):
|
||||
return {decode_bulk(k): decode_bulk(v) for k, v in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
def fetch_key(r: redis.Redis, key: bytes) -> Tuple[str, Any]:
|
||||
key_s = decode_bulk(key)
|
||||
t = r.type(key)
|
||||
if isinstance(t, bytes):
|
||||
t = t.decode()
|
||||
|
||||
if t == "string":
|
||||
val = r.get(key)
|
||||
return t, decode_bulk(val)
|
||||
|
||||
if t == "list":
|
||||
# LRANGE 0 -1
|
||||
vals = r.lrange(key, 0, -1)
|
||||
return t, decode_bulk(vals)
|
||||
|
||||
if t == "set":
|
||||
vals = r.smembers(key)
|
||||
# sets are unordered; convert to sorted list for stable YAML
|
||||
return t, sorted(decode_bulk(vals))
|
||||
|
||||
if t == "zset":
|
||||
# Withscores=True to preserve order/score
|
||||
vals = r.zrange(key, 0, -1, withscores=True)
|
||||
# Represent as list of {member, score}
|
||||
out = [{"member": decode_bulk(m), "score": float(s)} for m, s in vals]
|
||||
return t, out
|
||||
|
||||
if t == "hash":
|
||||
vals = r.hgetall(key)
|
||||
return t, decode_bulk(vals)
|
||||
|
||||
# Unknown or none
|
||||
return t, None
|
||||
|
||||
|
||||
def dump_redis_to_yaml(
|
||||
host: str,
|
||||
port: int,
|
||||
db: int,
|
||||
password: str | None,
|
||||
output_path: str,
|
||||
match: str | None,
|
||||
scan_count: int,
|
||||
include_ttl: bool,
|
||||
):
|
||||
r = redis.Redis(host=host, port=port, db=db, password=password)
|
||||
|
||||
dump: Dict[str, Any] = {"meta": {"host": host, "port": port, "db": db}, "data": {}}
|
||||
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, keys = r.scan(cursor=cursor, match=match, count=scan_count)
|
||||
for key in keys:
|
||||
key_s = decode_bulk(key)
|
||||
t, val = fetch_key(r, key)
|
||||
entry = {"type": t, "value": val}
|
||||
if include_ttl:
|
||||
ttl = r.ttl(key)
|
||||
entry["ttl"] = ttl # seconds; -1 no expire, -2 key missing
|
||||
dump["data"][key_s] = entry
|
||||
if cursor == 0:
|
||||
break
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(dump, f, sort_keys=True, allow_unicode=True)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description="Dump a Redis DB to a YAML file.")
|
||||
parser.add_argument("--host", default="127.0.0.1", help="Redis host")
|
||||
parser.add_argument("--port", type=int, default=6379, help="Redis port")
|
||||
parser.add_argument("--db", type=int, default=0, help="Redis DB index")
|
||||
parser.add_argument("--password", default=None, help="Redis password")
|
||||
parser.add_argument("-o", "--output", required=True, help="Output YAML file path")
|
||||
parser.add_argument(
|
||||
"--match",
|
||||
default=None,
|
||||
help="Key pattern for SCAN (e.g., 'user:*'). If omitted, all keys are scanned.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--scan-count",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="Hint for SCAN per iteration (not a limit).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-ttl",
|
||||
action="store_true",
|
||||
help="Include TTL (seconds) for each key.",
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
dump_redis_to_yaml(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
db=args.db,
|
||||
password=args.password,
|
||||
output_path=args.output,
|
||||
match=args.match,
|
||||
scan_count=args.scan_count,
|
||||
include_ttl=args.include_ttl,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,15 @@
|
||||
from aaredaq.daq import AareDAQ
|
||||
from mxlibs3.jfjoch import JFJochWrapper
|
||||
from aaredaqlib.beamline import MXBeamline
|
||||
from aaredaq.aaredb import AareWrapper
|
||||
from aaredaq.config import BeamlineConfig
|
||||
from aaredaqlib.diffraction_geometry import DiffractionGeometry
|
||||
from aaredaq.devices import BeamlineDevices
|
||||
|
||||
j = JFJochWrapper(MXBeamline.X06DA)
|
||||
a = AareWrapper(MXBeamline.X06DA)
|
||||
c = BeamlineConfig(MXBeamline.X06DA)
|
||||
d = BeamlineDevices(MXBeamline.X06DA)
|
||||
daq = AareDAQ(bl=MXBeamline.X06DA, cfg=c)
|
||||
result = j.wait_till_done(60)
|
||||
a.ingest_scan(sample=c.current_sample, result=result, geom= daq.sample_geometry,beam_mark_pxl=c.get_beam_mark(d.zoom))
|
||||
Reference in New Issue
Block a user