diff --git a/configuration/camera_config/ABODI-SRM-6.json b/configuration/camera_config/ABODI-SRM-6.json index 1e43720..ea9e98c 100644 --- a/configuration/camera_config/ABODI-SRM-6.json +++ b/configuration/camera_config/ABODI-SRM-6.json @@ -1,8 +1,8 @@ { "camera_calibration": { "reference_marker": [ - 100, - 100, + 44, + 44, 1999, 1999 ], diff --git a/configuration/pipeline_config/ABODI-SRM-6_proc.json b/configuration/pipeline_config/ABODI-SRM-6_proc.json new file mode 100644 index 0000000..3db298e --- /dev/null +++ b/configuration/pipeline_config/ABODI-SRM-6_proc.json @@ -0,0 +1,18 @@ +{ + "image_background_enable": false, + "image_background": null, + "image_threshold": null, + "image_region_of_interest": null, + "image_good_region": { + "threshold": 0.4, + "gfscale": 3.0 + }, + "image_slices": null, + "pipeline_type": "processing", + "camera_name": "ABODI-SRM-6", + "function": "epics_forwarder_gr.py", + "name": "ABODI-SRM-6_proc", + "max_frame_rate": 10.1, + "throw_epics_errors": true, + "reload": true +} \ No newline at end of file diff --git a/configuration/pipeline_config/X01DD-FE-CAM1_proc.json b/configuration/pipeline_config/X01DD-FE-CAM1_proc.json index a8a5a27..8b87613 100644 --- a/configuration/pipeline_config/X01DD-FE-CAM1_proc.json +++ b/configuration/pipeline_config/X01DD-FE-CAM1_proc.json @@ -2,11 +2,17 @@ "image_background_enable": false, "image_background": null, "image_threshold": null, - "image_region_of_interest": null, + "image_region_of_interest": [ + 1807, + 1251, + 1506, + 1077 + ], "image_good_region": { "threshold": 0.4, "gfscale": 3.0 }, + "averaging": 15, "image_slices": null, "pipeline_type": "processing", "camera_name": "X01DD-FE-CAM1", diff --git a/configuration/pipeline_config/X01DD-FE-CAM1_sp.json b/configuration/pipeline_config/X01DD-FE-CAM1_sp.json index 617f063..9244bba 100644 --- a/configuration/pipeline_config/X01DD-FE-CAM1_sp.json +++ b/configuration/pipeline_config/X01DD-FE-CAM1_sp.json @@ -1,6 +1,6 @@ { "image_background_enable": false, - "image_background": null, + "image_background": "X01DD-FE-CAM1_20251202_165201_161057", "image_threshold": null, "image_region_of_interest": null, "image_good_region": { @@ -11,5 +11,11 @@ "pipeline_type": "processing", "camera_name": "X01DD-FE-CAM1", "name": "X01DD-FE-CAM1_sp", - "max_frame_rate": 10.1 + "max_frame_rate": 100.1, + "reload": true, + "function": "epics_handler.py", + "max_output_rate": 10.1, + "single_optics": true, + "PSF": 3.4, + "throw_epics_errors": true } \ No newline at end of file diff --git a/configuration/pipeline_config/X08DB-FE-CAM1_proc.json b/configuration/pipeline_config/X08DB-FE-CAM1_proc.json index 75e3e26..1936eb6 100644 --- a/configuration/pipeline_config/X08DB-FE-CAM1_proc.json +++ b/configuration/pipeline_config/X08DB-FE-CAM1_proc.json @@ -3,6 +3,7 @@ "image_background": null, "image_threshold": null, "image_region_of_interest": null, + "averaging": 15, "image_good_region": { "threshold": 0.4, "gfscale": 3.0 diff --git a/configuration/pipeline_config/X08DB-FE-CAM1_sp.json b/configuration/pipeline_config/X08DB-FE-CAM1_sp.json index 1f2fd78..ea23d6a 100644 --- a/configuration/pipeline_config/X08DB-FE-CAM1_sp.json +++ b/configuration/pipeline_config/X08DB-FE-CAM1_sp.json @@ -11,5 +11,10 @@ "pipeline_type": "processing", "camera_name": "X08DB-FE-CAM1", "name": "X08DB-FE-CAM1_sp", - "max_frame_rate": 10.1 + "function": "epics_handler.py", + "max_frame_rate": 100.1, + "max_output_rate": 10.1, + "single_optics": true, + "PSF": 3.4, + "throw_epics_errors": true } \ No newline at end of file diff --git a/configuration/pipeline_config/permanent_instances.json b/configuration/pipeline_config/permanent_instances.json index 8e2be28..e491522 100644 --- a/configuration/pipeline_config/permanent_instances.json +++ b/configuration/pipeline_config/permanent_instances.json @@ -1,11 +1,11 @@ { "#ALBDI-SM-2_emittance": "ALBDI-SM-2_emittance", "#ALIDI-SM-5_proc": "ALIDI-SM-5_proc", - "#X01DD-FE-CAM1_sp": "X01DD-FE-CAM1_sp", "#simulation_proc_sp": "simulation_proc_sp", "#simulation_sp1": "simulation_sp", "ABODI-SM-1S1_proc": "ABODI-SM-1S1_proc", "ABODI-SM-1S2_proc": "ABODI-SM-1S2_proc", + "ABODI-SRM-6_proc": "ABODI-SRM-6_proc", "ABRTL-DSCR-0440_proc": "ABRTL-DSCR-0440_proc", "ABRTL-DSCR-0680_proc": "ABRTL-DSCR-0680_proc", "ABRTL-DSCR-0870_proc": "ABRTL-DSCR-0870_proc", @@ -25,8 +25,8 @@ "ARS05-RCAM-0310_proc2": "ARS05-RCAM-0310_proc2", "X01DD-ES-CAM1_emittance": "X01DD-ES-CAM1_emittance", "X01DD-ES-CAM1_sp_proc": "X01DD-ES-CAM1_sp_proc", - "X01DD-FE-CAM1_proc": "X01DD-FE-CAM1_proc", + "X01DD-FE-CAM1_sp": "X01DD-FE-CAM1_sp", "X01DD-SC-CAM1_proc": "X01DD-SC-CAM1_proc", - "X08DB-FE-CAM1_proc": "X08DB-FE-CAM1_proc", + "X08DB-FE-CAM1_sp": "X08DB-FE-CAM1_sp", "emittance_pipolar": "emittance_pipolar" } \ No newline at end of file diff --git a/configuration/pipeline_config/servers.json b/configuration/pipeline_config/servers.json index 139714c..2673a8b 100644 --- a/configuration/pipeline_config/servers.json +++ b/configuration/pipeline_config/servers.json @@ -38,7 +38,7 @@ "instances": [ "ALIDI-SM-5_sp_proc", "ALBDI-SM-2_sp_proc", - "#X01DD-FE-CAM1_sp", + "X01DD-FE-CAM1_sp", "ALBDI-SM-2_emittance:9001", "X01DD-ES-CAM1_emittance", "X01DD-FE-CAM1_proc", @@ -58,6 +58,8 @@ ], "enabled": true, "expanding": false, - "instances": [] + "instances": [ + "ABODI-SRM-6_proc" + ] } } \ No newline at end of file diff --git a/configuration/user_scripts/epics_forwarder_gr.py b/configuration/user_scripts/epics_forwarder_gr.py index c8b4d9a..9c36284 100644 --- a/configuration/user_scripts/epics_forwarder_gr.py +++ b/configuration/user_scripts/epics_forwarder_gr.py @@ -16,7 +16,7 @@ mean_x_name, mean_y_name, sig_x_name, sig_y_name = None, None, None, None ampl_x_pv, ampl_y_pv, integ_x_pv, integ_y_pv = None, None, None, None ampl_x_name, ampl_y_name, integ_x_name, integ_y_name = None, None, None, None sent_pid = -1 -PSF = 2.91 # um +PSF = 3.4# 3.4um updated 11/11/25 at 11:16hrs initialized = False @@ -36,7 +36,10 @@ def initialize(parameters): initialized = True def hypot_diff(a, b): - return math.sqrt(a**2 - b**2) + try: + return math.sqrt(a**2 - b**2) + except: + return 0.0 def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata): global initialized, sent_pid diff --git a/configuration/user_scripts/epics_handler.py b/configuration/user_scripts/epics_handler.py new file mode 100644 index 0000000..7a10e14 --- /dev/null +++ b/configuration/user_scripts/epics_handler.py @@ -0,0 +1,111 @@ +from cam_server.pipeline.data_processing import processor +from cam_server.utils import create_thread_pvs, epics_lock +from logging import getLogger +import math + +_logger = getLogger(__name__) + +#Relevant parameters +# max_frame_rate #max processing frame rate (EPICS) +# max_output_rate #max displaying frame rate (ScreenPanel) +# single_optics #default: False +# PSF #defauit 2.91um +# throw_epics_errors #in case of permanent pipelines can recreate EPICS channels after failure, but does stream + +#Output PVs +x_pos_pv, y_pos_pv, x_sig_pv, y_sig_pv = None, None, None, None +x_amp_pv, y_amp_pv, x_int_pv, y_int_pv = None, None, None, None + +#Channel names +x_pos_name, y_pos_name, x_sig_name, y_sig_name = None, None, None, None +x_amp_name, y_amp_name, x_int_name, y_int_name = None, None, None, None +sent_pid = -1 + +initialized = False +run_once = False +GAUSS_INT_FACTOR = math.sqrt(2 * math.pi) + +def initialize(parameters): + global initialized + global x_pos_name, y_pos_name, x_sig_name, y_sig_name, x_amp_name, y_amp_name, x_int_name, y_int_name + prefix = parameters["camera_name"] + x_pos_name = prefix + ":POS-X" + y_pos_name = prefix + ":POS-Y" + x_sig_name = prefix + ":SIG-X" + y_sig_name = prefix + ":SIG-Y" + x_amp_name = prefix + ":AMPL-X" + y_amp_name = prefix + ":AMPL-Y" + x_int_name = prefix + ":INTEG-X" + y_int_name = prefix + ":INTEG-Y" + initialized = True + +def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata): + global initialized, sent_pid, run_once + global x_pos_pv, y_pos_pv, x_sig_pv, y_sig_pv, x_amp_pv, y_amp_pv, x_int_pv, y_int_pv + global x_pos_name, y_pos_name, x_sig_name, y_sig_name, x_amp_name, y_amp_name, x_int_name, y_int_name + + if not initialized: + initialize(parameters) + initialized = True + + throw_epics_errors = parameters.get("throw_epics_errors", True) + single_optics = parameters.get("single_optics", False) + PSF = parameters.get("PSF", 2.91) # um + good_region = parameters["image_good_region"] + detached = parameters.get("detached_instance", False) + + ret = processor.process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata) + + if good_region: + x_pos_key, y_pos_key = "gr_x_fit_mean", "gr_y_fit_mean" + x_sig_key, y_sig_key = "gr_x_fit_standard_deviation", "gr_y_fit_standard_deviation" + x_amp_key, y_amp_key = "gr_x_fit_amplitude", "gr_y_fit_amplitude" + else: + x_pos_key, y_pos_key = "x_fit_mean", "y_fit_mean" + x_sig_key, y_sig_key = "x_fit_standard_deviation", "y_fit_standard_deviation" + x_amp_key, y_amp_key = "x_fit_amplitude", "y_fit_amplitude" + + x_pos, y_pos = ret[x_pos_key], ret[y_pos_key] + x_sig, y_sig = ret[x_sig_key], ret[y_sig_key] + x_amp, y_amp = ret[x_amp_key], ret[y_amp_key] + x_int, y_int = x_amp * x_sig * GAUSS_INT_FACTOR, y_amp * y_sig * GAUSS_INT_FACTOR + + if single_optics: + x_sig, y_sig = math.hypot(x_sig, PSF), math.hypot(y_sig, PSF) + ret[x_sig_key], ret[y_sig_key] = x_sig, y_sig + + x_pos_pv, y_pos_pv, x_sig_pv, y_sig_pv, x_amp_pv, y_amp_pv, x_int_pv, y_int_pv = create_thread_pvs( + [x_pos_name, y_pos_name, x_sig_name, y_sig_name, x_amp_name, y_amp_name, x_int_name, y_int_name]) + + if not detached: #Only write to EPICS in the "main" instance + epics_error = None + + if pulse_id > sent_pid: + sent_pid = pulse_id + + if epics_lock.acquire(False): + try: + for (pv, value) in [(x_pos_pv, x_pos), (y_pos_pv, y_pos), (x_sig_pv, x_sig), (y_sig_pv, y_sig), + (x_amp_pv, x_amp), (y_amp_pv, y_amp), (x_int_pv, x_int), (y_int_pv, y_int)]: + if pv and pv.connected: + pv.put(value) + else: + if not run_once: + _logger.warning("PV not connected: %s" % (str(pv),)) + except Exception as e: + epics_error = "Error writing PVs: %s" % (str(e),) + finally: + epics_lock.release() + else: + _logger.warning("Cannot aquire EPICS lock") + + if epics_error: + _logger.warning(epics_error) + if throw_epics_errors: + raise Exception(epics_error) + run_once = True + return ret + else: + _logger.warning("Invalid PID: %s last:%s" % (str(pulse_id), str(sent_pid),)) + sent_pid = 0 # It should be single threaded, assumes the camera instance restarted + diff --git a/configuration/user_scripts/pipolar.py b/configuration/user_scripts/pipolar.py index 34514f8..64fd34d 100644 --- a/configuration/user_scripts/pipolar.py +++ b/configuration/user_scripts/pipolar.py @@ -213,7 +213,7 @@ def calculate_emittance(image, fit_pars): ratio_corrected = 2 * (valley_fitted_value) / (abs(peak_value[0] + peak_value[1])) idx = 0 if abs(peak_value[0] ) > abs(peak_value[1] ) else 1 - ratio_max = 0.33 # abs(peak_value[idx]) #valley_fitted_value / + ratio_max = valley_fitted_value /abs(peak_value[idx]) emittance = ppol.get_emittance(ratio_corrected) emittance2 = ppol.get_emittance(ratio_max) diff --git a/configuration/user_scripts/sp_pipolar.py b/configuration/user_scripts/sp_pipolar.py index 6ae9a5f..b0b1e40 100644 --- a/configuration/user_scripts/sp_pipolar.py +++ b/configuration/user_scripts/sp_pipolar.py @@ -1,12 +1,225 @@ import sys import os -from emittance import calculate_emittance from cam_server.pipeline.data_processing import functions, processor +import numpy as np +from scipy import interpolate +from scipy.signal import find_peaks +import math +from logging import getLogger + +_logger = getLogger(__name__) + + +class PpolPeakValley(): + def __init__(self): + self.x, self.y = self.ppol_interpol() + + def ppol_interpol(self, plot_flag=False): + ipv = [0.0, 32.976, 20.848, 15.306, 12.121, 10.050, 8.595, 7.518, 6.687, 6.028, 5.491, 5.047, 4.673, 4.353, 4.078, 3.837, 3.439, 3.122, 2.865, 2.652, 2.473, 2.320] + sig = [0.0, 3.659, 5.175, 6.338, 7.318, 8.182, 8.963, 9.681, 10.350, 10.978, 11.572, 12.136, 12.676, 13.194, 13.692, 14.172, 15.087, 15.950, 16.769, 17.549, 18.296, 19.014] + #ipv = [0.0, 43.237,26.505,19.129,14.978,12.316,10.466,9.104,8.061,7.237,6.569,6.017,5.553,5.159,4.819,4.523,4.033,3.645,3.330,3.070,2.851,2.666] + #sig = [0.0, 3.659, 5.175, 6.338, 7.318, 8.182, 8.963, 9.681, 10.350, 10.978, 11.572, 12.136, 12.676, 13.194, 13.692, 14.172, 15.087, 15.950, 16.769, 17.549, 18.296, 19.014] + sig2 = [element * element for element in sig] + x = np.linspace(0, 999999, 999999) + x = [(1+val)/1000000*max(ipv) for val in x] + interpfunc = interpolate.interp1d(ipv, sig2, kind='quadratic') + inter_x = interpfunc(x) + sqrt_val = [math.sqrt(val) for val in inter_x] + y = [0] + y.extend(sqrt_val) + y.append(max(sig)) + xf = [0] + xf.extend(x) + xf.append(max(ipv)) + return xf, y + + def get_emittance(self, ratio): + emittance = 0 + for i in range(0, len(self.x)): + if self.x[i] > ratio: + self.y[i-1] + return self.y[i-1] + +#For peak search - delta(h) to max peak value +DELTA_HEIGHT = 60000 +BG_XRANGE_LOW = [0, 200] +BG_XRANGE_HIGH = [1921, 2112] + +PEAK_SEARCH_REL_RANGE = [-2, 3] +VALLEY_SEARCH_REL_RANGE = [-2, 3] + +ppol = PpolPeakValley() + +# abreviations +BX1 = BG_XRANGE_LOW[0] +BX2 = BG_XRANGE_LOW[1] +BX3 = BG_XRANGE_HIGH[0] +BX4 = BG_XRANGE_HIGH[1] +plr = PEAK_SEARCH_REL_RANGE +vlr = VALLEY_SEARCH_REL_RANGE + +ydata = [] + + +def calculate_emittance(image, fit_pars): + global ydata # array of indexes + DELTA_HEIGHT = fit_pars['delta_height'] + BG_XRANGE_LOW = fit_pars['bg_range_low'] + BG_XRANGE_HIGH = fit_pars['bg_range_high'] + PEAK_SEARCH_REL_RANGE = fit_pars['peak_search_rel_range'] + VALLEY_SEARCH_REL_RANGE = fit_pars['valley_search_rel_range'] + + w, h = len(image[0]), len(image) + if len(ydata) != h: + H = [] + for i in range(0, h): + H.append(i) + ydata = H[:int(h)] + + peak_array = [None] * 2 + proj_peak_array = [None] * 2 + peak_value = [None] * 2 + peak_bg = [None] * 2 + image -= image.min() + projy = np.sum(image, axis=1) + projx = np.sum(image, axis=0) + # find peaks + max_element = np.amax(projy) + max_indices = np.where(projy == max_element) + peaks, _ = find_peaks(projy, height=(max_element - DELTA_HEIGHT)) + + _logger.debug("max indices /peaks " + str(max_indices) + " " + str(peaks)) + + if len(peaks) != 2: + mess = "Too few peaks found! " if len(peaks) < 2 else \ + "Too many peaks found " + _logger.debug(mess + str(peaks)) + peaks_buffer = [] + for val in peaks: + ### COMMENTED BY ALEX + # if val > 567 and val < 590: + peaks_buffer.append(val) + + # if len(peaks_buffer) ==3: + # peaks = [None] * 2 + # peaks[0] = peaks_buffer[0] + # peaks[1] = peaks_buffer[2] + if len(peaks_buffer) != 2: + return (-1.0, -2.0) + else: + peaks = peaks_buffer + + if (peaks[1] - peaks[0]) < 6: + _logger.debug("Peaks are too close: " + str(peaks[1] - peaks[0])) + raise Exception("Peaks are too close") + + # peaks =[569, 577] + # Distance to minimum + min_element = np.amin(projy[peaks[0]:peaks[1]]) + min_indices = np.where(projy == min_element) + + min_idx_value = 0 # min_indices[0][0] + for val in min_indices[0]: + if val > peaks[0] and val < peaks[1]: + min_idx_value = val + break + + if min_idx_value == 0: + raise Exception("min_idx_value == 0") + + for i in range(0, len(peak_array)): + peak_array[i] = ydata[peaks[i] + plr[0]: peaks[i] + plr[1]] + valley_array = ydata[min_idx_value + vlr[0]: min_idx_value + vlr[1]] + + # print("peaks", peaks, flush=True) + # print(np.subtract(peaks, h)*(-1)) + # print("projections peak, valley", projy[(peaks[0]-1):(peaks[1]+2)], projy[valley_array]) + # print(peak_array, valley_array, flush=True) + # x_bg_center = min_indices[0][0] + + # background + bg_y1 = projy[BX1: BX2] + bg_y2 = projy[BX3: BX4] + + bg_yS = np.concatenate((bg_y1, bg_y2)) + bg_xS = list(range(BX1, BX2)) + list(range(BX3, BX4)) + + bg_x = [] + bg_y = [] + + for x, y in zip(bg_xS, bg_yS): + ### COMMENTED BY ALEX + # if y > 800 and y < 1000: + bg_x.append(x) + bg_y.append(y) + + # print(bg_x, flush=True) + # print(bg_y, flush=True) + # fit + poly_bg = np.polyfit(bg_x, bg_y, deg=1) + array_bg = np.linspace(0, h, 10400) + val_bg = np.polyval(poly_bg, array_bg) + + for i in range(0, len(proj_peak_array)): + proj_peak_array[i] = projy[peaks[i] + plr[0]: peaks[i] + plr[1]] + # proj_peak_array[1] = projy[peaks[1]-1 : peaks[1]+2] + proj_valley = projy[min_idx_value + vlr[0]: min_idx_value + vlr[1]] + + # peaks + for i in range(0, 2): + poly = np.polyfit(peak_array[i], proj_peak_array[i], deg=2) + idx = -poly[1] / 2 / poly[0] + peak_value[i] = np.polyval(poly, idx) + peak_bg[i] = val_bg[int(idx)] + + # valley + poly2 = np.polyfit(valley_array, proj_valley, deg=2) + # Only works for deg=2 + minv_idx = -poly2[1] / 2 / poly2[0] + + poly = np.polyfit(valley_array, proj_valley, deg=4) + + valley_subarray = np.linspace(valley_array[0], valley_array[-1], 800) + poly_array = np.polyval(poly, valley_subarray) + valley_fitted_value = min(poly_array) + + # print("peak value", peak_value, "valley_fitted value", valley_fitted_value, flush=True) + + valley_bg = val_bg[int(minv_idx)] + # print("valley background", valley_bg, "peak background", peak_bg[0], peak_bg[1], flush=True) + + ### COMMENTED BY ALEX + # if valley_fitted_value < valley_bg: + # valley_fitted_value = min( projy[valley_array]) + # if valley_fitted_value < valley_bg: + # raise Exception("valley_fitted_value < valley_bg") + # #return + + #ratio_corrected = abs((peak_value[0] - peak_bg[0]) + (peak_value[1] - peak_bg[1])) / (2 * (valley_fitted_value - valley_bg)) + + #idx = 0 if abs(peak_value[0] - peak_bg[0]) > abs(peak_value[1] - peak_bg[1]) else 1 + #ratio_max = abs(peak_value[idx] - peak_bg[idx]) / (valley_fitted_value - valley_bg) + + ratio_corrected = (abs(peak_value[0] + peak_value[1])) / (2 * valley_fitted_value) + idx = 0 if abs(peak_value[0] ) > abs(peak_value[1] ) else 1 + ratio_max = abs(peak_value[idx]) / valley_fitted_value + + emittance = ppol.get_emittance(ratio_corrected) + emittance2 = ppol.get_emittance(ratio_max) + + _logger.debug("ratio=%f emittance %f ratio2=%f emittance2 %f" % (ratio_corrected, emittance, ratio_max, emittance2)) + return (emittance, emittance2) + + + + + + def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata=None): ret = processor.process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata) - camera = parameters["camera_name"] + camera = parameters["camera_name"] emittance = emittance2 = float("NaN") status = "Ok" try: @@ -17,11 +230,11 @@ def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata except Exception as e: status="Error: " exc_type, exc_obj, exc_tb = sys.exc_info() - while exc_tb is not None: + while exc_tb is not None: fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] status = status + fname + " line " + str(exc_tb.tb_lineno) + ": " + str(e) + " | " exc_tb = exc_tb.tb_next - + ret["emittance"] = emittance ret["emittance2"] = emittance2 ret["emittance_status"] = str(status)