diff --git a/scripts/aerotech_auto1_tests.ipynb b/scripts/aerotech_auto1_tests.ipynb new file mode 100644 index 00000000..7111a5d2 --- /dev/null +++ b/scripts/aerotech_auto1_tests.ipynb @@ -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 +} diff --git a/scripts/alc_bkg_comp.py b/scripts/alc_bkg_comp.py new file mode 100644 index 00000000..efa844ef --- /dev/null +++ b/scripts/alc_bkg_comp.py @@ -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() \ No newline at end of file diff --git a/scripts/auto1_test.py b/scripts/auto1_test.py new file mode 100644 index 00000000..406c13ae --- /dev/null +++ b/scripts/auto1_test.py @@ -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") \ No newline at end of file diff --git a/scripts/bec_client.py b/scripts/bec_client.py new file mode 100644 index 00000000..72032420 --- /dev/null +++ b/scripts/bec_client.py @@ -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() \ No newline at end of file diff --git a/scripts/camera_stat_thread.py b/scripts/camera_stat_thread.py new file mode 100644 index 00000000..43dcdac8 --- /dev/null +++ b/scripts/camera_stat_thread.py @@ -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() \ No newline at end of file diff --git a/scripts/free_busy_state.py b/scripts/free_busy_state.py new file mode 100644 index 00000000..e53d4b08 --- /dev/null +++ b/scripts/free_busy_state.py @@ -0,0 +1,4 @@ +from aaredaq.config import BeamlineConfig +from aaredaqlib.beamline import MXBeamline +c = BeamlineConfig(MXBeamline.X06DA) +c.state_busy = False \ No newline at end of file diff --git a/scripts/pshell_client_old.py b/scripts/pshell_client_old.py new file mode 100644 index 00000000..aaa223bd --- /dev/null +++ b/scripts/pshell_client_old.py @@ -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=""): + """Returns help or auto-completion strings. + + Args: + input(optional, str): - ":" for control commands + - "" 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)) diff --git a/scripts/redis_dump.py b/scripts/redis_dump.py new file mode 100644 index 00000000..f9e40423 --- /dev/null +++ b/scripts/redis_dump.py @@ -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()) \ No newline at end of file diff --git a/scripts/test_scan_ingest.py b/scripts/test_scan_ingest.py new file mode 100644 index 00000000..bf8e1ffc --- /dev/null +++ b/scripts/test_scan_ingest.py @@ -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)) \ No newline at end of file