diff --git a/configuration/camera_config/S10DI01-DSCR020.json b/configuration/camera_config/S10DI01-DSCR020.json index f46ce92..60e3632 100644 --- a/configuration/camera_config/S10DI01-DSCR020.json +++ b/configuration/camera_config/S10DI01-DSCR020.json @@ -16,11 +16,12 @@ "rotate": 0, "roi": null, "image_background": null, - "source_type": "epics", + "source_type": "bsread", "name": "S10DI01-DSCR020", "source": "S10DI01-DSCR020", "group": [ "Electrons" ], - "alias": [] + "alias": [], + "forwarder_port": 9016 } \ No newline at end of file diff --git a/configuration/camera_config/SARES20-CAMS142-M1.json b/configuration/camera_config/SARES20-CAMS142-M1.json index 16e001d..eb589f3 100644 --- a/configuration/camera_config/SARES20-CAMS142-M1.json +++ b/configuration/camera_config/SARES20-CAMS142-M1.json @@ -1,18 +1,18 @@ { "camera_calibration": { "reference_marker": [ - 533, - 592, - 535, - 594 + 642, + 497, + 644, + 499 ], - "reference_marker_width": 10.256410256410257, - "reference_marker_height": 10.256410256410257, + "reference_marker_width": 16.256, + "reference_marker_height": 16.256, "angle_horizontal": 0.0, "angle_vertical": 0.0 }, "mirror_x": false, - "mirror_y": false, + "mirror_y": true, "rotate": 0, "roi": null, "image_background": null, diff --git a/configuration/camera_config/SARES20-CAMS142-M2.json b/configuration/camera_config/SARES20-CAMS142-M2.json index 8c86537..e7fae46 100644 --- a/configuration/camera_config/SARES20-CAMS142-M2.json +++ b/configuration/camera_config/SARES20-CAMS142-M2.json @@ -11,7 +11,7 @@ "angle_horizontal": 0.0, "angle_vertical": 0.0 }, - "mirror_x": false, + "mirror_x": true, "mirror_y": true, "rotate": 3, "roi": null, diff --git a/configuration/camera_config/SARES20-CAMS142-M3.json b/configuration/camera_config/SARES20-CAMS142-M3.json index d0591af..e859bca 100644 --- a/configuration/camera_config/SARES20-CAMS142-M3.json +++ b/configuration/camera_config/SARES20-CAMS142-M3.json @@ -1,10 +1,10 @@ { "camera_calibration": { "reference_marker": [ - 1068, - 1017, - 1070, - 1019 + 1492, + 705, + 1494, + 707 ], "reference_marker_width": 19.333333333333332, "reference_marker_height": 22.4, diff --git a/configuration/camera_config/SARES30-CAMS156-XE.json b/configuration/camera_config/SARES30-CAMS156-XE.json index d05216d..c562a0a 100644 --- a/configuration/camera_config/SARES30-CAMS156-XE.json +++ b/configuration/camera_config/SARES30-CAMS156-XE.json @@ -1,13 +1,13 @@ { "camera_calibration": { "reference_marker": [ - 485, - 261, - 1075, - 971 + 600, + 343, + 999, + 745 ], - "reference_marker_width": 1200.0, - "reference_marker_height": 1000.0, + "reference_marker_width": 200.0, + "reference_marker_height": 200.0, "angle_horizontal": 0.0, "angle_vertical": 0.0 }, diff --git a/configuration/camera_config/SATES21-CAMS154-M1.json b/configuration/camera_config/SATES21-CAMS154-M1.json index 5b04f80..e7aa97b 100644 --- a/configuration/camera_config/SATES21-CAMS154-M1.json +++ b/configuration/camera_config/SATES21-CAMS154-M1.json @@ -14,6 +14,8 @@ "mirror_x": false, "mirror_y": false, "rotate": 0, + "roi": null, + "image_background": null, "source_type": "bsread", "name": "SATES21-CAMS154-M1", "prefix": "SATES21-CAMS154-M1", @@ -27,5 +29,6 @@ "Photonics", "Maloja" ], - "alias": [] + "alias": [], + "no_client_timeout": 0 } \ No newline at end of file diff --git a/configuration/camera_config/SATES30-CAMS182-GIGE6.json b/configuration/camera_config/SATES30-CAMS182-GIGE6.json new file mode 100644 index 0000000..d8d90f8 --- /dev/null +++ b/configuration/camera_config/SATES30-CAMS182-GIGE6.json @@ -0,0 +1,28 @@ +{ + "camera_calibration": { + "reference_marker": [ + 100, + 100, + 113, + 113 + ], + "reference_marker_width": 500.0, + "reference_marker_height": 500.0, + "angle_horizontal": 0.0, + "angle_vertical": 0.0 + }, + "mirror_x": true, + "mirror_y": false, + "rotate": 0, + "roi": null, + "image_background": null, + "source_type": "bsread", + "protocol": "tcp", + "source": "SATES30-CAMS182-GIGE6", + "name": "SATES30-CAMS182-GIGE6", + "prefix": "SATES30-CAMS182-GIGE6", + "group": [ + "Photonics", + "Furka" + ] +} \ No newline at end of file diff --git a/configuration/camera_config/SLAAR21-LCAM-C561.json b/configuration/camera_config/SLAAR21-LCAM-C561.json index f468ae5..e20b600 100644 --- a/configuration/camera_config/SLAAR21-LCAM-C561.json +++ b/configuration/camera_config/SLAAR21-LCAM-C561.json @@ -1,13 +1,13 @@ { "camera_calibration": { "reference_marker": [ - 42, - 37, - 50, - 44 + 98, + 85, + 109, + 96 ], - "reference_marker_width": 8.0, - "reference_marker_height": 7.0, + "reference_marker_width": 58.0, + "reference_marker_height": 58.4, "angle_horizontal": 0.0, "angle_vertical": 0.0 }, diff --git a/configuration/camera_config/SLAAR21-LCAM-C562.json b/configuration/camera_config/SLAAR21-LCAM-C562.json index 677a722..af02bc8 100644 --- a/configuration/camera_config/SLAAR21-LCAM-C562.json +++ b/configuration/camera_config/SLAAR21-LCAM-C562.json @@ -1,13 +1,13 @@ { "camera_calibration": { "reference_marker": [ - 540, - 373, - 554, - 382 + 662, + 449, + 759, + 548 ], - "reference_marker_width": 104.0, - "reference_marker_height": 108.0, + "reference_marker_width": 514.0, + "reference_marker_height": 525.0, "angle_horizontal": 0.0, "angle_vertical": 0.0 }, diff --git a/configuration/camera_config/SLAAR21-LCAM-C563.json b/configuration/camera_config/SLAAR21-LCAM-C563.json index 855158c..e619841 100644 --- a/configuration/camera_config/SLAAR21-LCAM-C563.json +++ b/configuration/camera_config/SLAAR21-LCAM-C563.json @@ -1,13 +1,13 @@ { "camera_calibration": { "reference_marker": [ - 364, - 250, - 468, - 358 + 72, + 49, + 83, + 59 ], - "reference_marker_width": 104.0, - "reference_marker_height": 108.0, + "reference_marker_width": 58.0, + "reference_marker_height": 53.0, "angle_horizontal": 0.0, "angle_vertical": 0.0 }, diff --git a/configuration/camera_config/permanent_instances.json b/configuration/camera_config/permanent_instances.json index 264b6ca..780d3d8 100644 --- a/configuration/camera_config/permanent_instances.json +++ b/configuration/camera_config/permanent_instances.json @@ -8,6 +8,7 @@ "#SATES21-ADTEST1-CAM5": "SATES21-ADTEST1-CAM5", "#SINBC02-DSRM310": "SINBC02-DSRM310", "S10BD01-DSCR030": "S10BD01-DSCR030", + "S10DI01-DSCR020": "S10DI01-DSCR020", "SARCL01-DSCR170": "SARCL01-DSCR170", "SARES11-SPEC125-M1": "SARES11-SPEC125-M1", "SARES11-SPEC125-M2": "SARES11-SPEC125-M2", diff --git a/configuration/camera_config/servers.json b/configuration/camera_config/servers.json index 840e295..ddf4ed3 100644 --- a/configuration/camera_config/servers.json +++ b/configuration/camera_config/servers.json @@ -41,6 +41,7 @@ "SATBD01-DSCR120", "SARBD01-DSCR110", "S10BD01-DSCR030", + "S10DI01-DSCR020", "SATBD02-DSCR050", "SARCL01-DSCR170", "SARCL02-DSCR280" diff --git a/configuration/pipeline_config/S10BC02-DSRM310_sp.json b/configuration/pipeline_config/S10BC02-DSRM310_sp.json index 315cb9a..bda73be 100644 --- a/configuration/pipeline_config/S10BC02-DSRM310_sp.json +++ b/configuration/pipeline_config/S10BC02-DSRM310_sp.json @@ -6,5 +6,6 @@ "image_good_region": null, "image_slices": null, "camera_name": "S10BC02-DSRM310", - "name": "S10BC02-DSRM310_sp" + "name": "S10BC02-DSRM310_sp", + "max_frame_rate": 10.1 } \ No newline at end of file diff --git a/configuration/pipeline_config/S10DI01-DSCR020_sp.json b/configuration/pipeline_config/S10DI01-DSCR020_sp.json index f72511a..5fe29b5 100644 --- a/configuration/pipeline_config/S10DI01-DSCR020_sp.json +++ b/configuration/pipeline_config/S10DI01-DSCR020_sp.json @@ -6,5 +6,6 @@ "image_good_region": null, "image_slices": null, "camera_name": "S10DI01-DSCR020", - "name": "S10DI01-DSCR020_sp" + "name": "S10DI01-DSCR020_sp", + "max_frame_rate": 10.1 } \ No newline at end of file diff --git a/configuration/pipeline_config/SARBD02-DSCR050_sp.json b/configuration/pipeline_config/SARBD02-DSCR050_sp.json index ccf4fd3..9d78fcb 100644 --- a/configuration/pipeline_config/SARBD02-DSCR050_sp.json +++ b/configuration/pipeline_config/SARBD02-DSCR050_sp.json @@ -1,12 +1,12 @@ { - "image_background_enable": false, - "image_background": null, + "image_background_enable": true, + "image_background": "SARBD02-DSCR050_20250305_182528_541069", "image_threshold": null, "image_region_of_interest": [ - 1213, - 329, - 576, - 812 + 1171, + 448, + 461, + 1364 ], "image_good_region": null, "image_slices": null, diff --git a/configuration/pipeline_config/SARBD02-DSCR051_sp.json b/configuration/pipeline_config/SARBD02-DSCR051_sp.json index baa0e74..0dd495d 100644 --- a/configuration/pipeline_config/SARBD02-DSCR051_sp.json +++ b/configuration/pipeline_config/SARBD02-DSCR051_sp.json @@ -1,11 +1,11 @@ { - "camera_calibration": null, "image_background_enable": false, - "image_background": null, + "image_background": "SARBD02-DSCR051_20250306_161408_842429", "image_threshold": null, "image_region_of_interest": null, "image_good_region": null, "image_slices": null, + "pipeline_type": "processing", "camera_name": "SARBD02-DSCR051", "name": "SARBD02-DSCR051_sp", "max_frame_rate": 5.1 diff --git a/configuration/pipeline_config/SARES11-SPEC125-M1_psen_db.json b/configuration/pipeline_config/SARES11-SPEC125-M1_psen_db.json index 9661a5d..612cd3f 100644 --- a/configuration/pipeline_config/SARES11-SPEC125-M1_psen_db.json +++ b/configuration/pipeline_config/SARES11-SPEC125-M1_psen_db.json @@ -1,6 +1,6 @@ { "image_background_enable": "passive", - "image_background": "SARES11-SPEC125-M1_20250315_152506_682314", + "image_background": "SARES11-SPEC125-M1_20250501_183259_735224", "image_threshold": null, "image_region_of_interest": null, "image_good_region": null, diff --git a/configuration/pipeline_config/SARES11-SPEC125-M2_db.json b/configuration/pipeline_config/SARES11-SPEC125-M2_db.json index 3d6e3aa..bd1793d 100644 --- a/configuration/pipeline_config/SARES11-SPEC125-M2_db.json +++ b/configuration/pipeline_config/SARES11-SPEC125-M2_db.json @@ -12,10 +12,10 @@ "mode": "PUSH", "allow_type_changes": false, "roi_signal": [ + 1000, 1200, - 1400, - 848, - 1448 + 1100, + 1700 ], "roi_background": [ 0, diff --git a/configuration/pipeline_config/SARES21-PBPS141_proc.json b/configuration/pipeline_config/SARES21-PBPS141_proc.json index b469373..846db95 100644 --- a/configuration/pipeline_config/SARES21-PBPS141_proc.json +++ b/configuration/pipeline_config/SARES21-PBPS141_proc.json @@ -26,10 +26,10 @@ "down": "SARES21-PBPS141:Lnk9Ch0-PP_VAL_PD2", "right": "SARES21-PBPS141:Lnk9Ch0-PP_VAL_PD3", "left": "SARES21-PBPS141:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 4.0268387189885095e-05, - "down_calib": 3.837274982195044e-05, - "left_calib": 0.00010659908000729991, - "right_calib": 5.903547370772529e-05, + "up_calib": 9.086696828684651e-06, + "down_calib": 8.479198152755808e-06, + "left_calib": 2.402225113860666e-05, + "right_calib": 1.253692752001019e-05, "horiz_calib": -5.93887, "vert_calib": -9.0057, "uJ_calib": 941.943984588351, @@ -94,5 +94,5 @@ 0.11052246554408188, 0.11072063526778356 ], - "calib_datetime": "2025-04-04 15:05:51" + "calib_datetime": "2025-05-06 11:19:16" } \ No newline at end of file diff --git a/configuration/pipeline_config/SARFE10-PBPS053_proc.json b/configuration/pipeline_config/SARFE10-PBPS053_proc.json index 402557c..7b99638 100644 --- a/configuration/pipeline_config/SARFE10-PBPS053_proc.json +++ b/configuration/pipeline_config/SARFE10-PBPS053_proc.json @@ -26,12 +26,12 @@ "down": "SARFE10-PBPS053:Lnk9Ch0-PP_VAL_PD2", "right": "SARFE10-PBPS053:Lnk9Ch0-PP_VAL_PD3", "left": "SARFE10-PBPS053:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 8.252737102787511e-05, - "down_calib": 9.932451384623452e-05, - "left_calib": 0.0002898544003376224, - "right_calib": 0.0001714541229915864, - "horiz_calib": -3.6052981040446994, - "vert_calib": -5.712227095363798, + "up_calib": 5.3603874230730805e-05, + "down_calib": 6.429273872665403e-05, + "left_calib": 0.0001873753953620842, + "right_calib": 0.00011135083709105292, + "horiz_calib": -3.5966302878741434, + "vert_calib": -5.704870102644, "uJ_calib": 834.5191797495979, "threshold": 0, "queue_length": 5000, @@ -69,9 +69,9 @@ 0.3 ], "calib_x_norm": [ - 0.08353620653149268, - 0.0003093021467825846, - -0.08288553799134554 + 0.08373324791431114, + 0.0002666207178174507, + -0.08308957010589782 ], "calib_y_range": [ -0.3, @@ -79,20 +79,20 @@ 0.3 ], "calib_y_norm": [ - 0.05297838244709519, - 0.00013686051846039786, - -0.052059458308041553 + 0.05334340044159048, + 6.423190047678002e-05, + -0.051829896970023125 ], "calib_time": "2022-11-28 16:19:37", - "calib_datetime": "2025-04-03 16:25:15", + "calib_datetime": "2025-05-12 19:38:08", "calib_x_norm_std": [ - 0.039097499054084266, - 0.04318884492921968, - 0.041594738954934915 + 0.03694393985351428, + 0.036381119656154114, + 0.037504981101666966 ], "calib_y_norm_std": [ - 0.047561029795043466, - 0.04584223560113521, - 0.04414664834114283 + 0.032515850998287014, + 0.031406268858134787, + 0.03357316145483791 ] } \ No newline at end of file diff --git a/configuration/pipeline_config/SARFE10-PSSS059_psss.json b/configuration/pipeline_config/SARFE10-PSSS059_psss.json index 07a0732..6786b89 100644 --- a/configuration/pipeline_config/SARFE10-PSSS059_psss.json +++ b/configuration/pipeline_config/SARFE10-PSSS059_psss.json @@ -8,7 +8,7 @@ "pipeline_type": "processing", "camera_name": "SARFE10-PSSS059", "name": "SARFE10-PSSS059_psss", - "function": "swissfel_spectral_processing.py", + "function": "swissfel_spectral_processing_fast.py", "mode": "PUSH", "allow_type_changes": false, "no_client_timeout": 0, diff --git a/configuration/pipeline_config/SAROP11-PBPS110_proc.json b/configuration/pipeline_config/SAROP11-PBPS110_proc.json index 6af4889..05e3c06 100644 --- a/configuration/pipeline_config/SAROP11-PBPS110_proc.json +++ b/configuration/pipeline_config/SAROP11-PBPS110_proc.json @@ -26,12 +26,12 @@ "down": "SAROP11-PBPS110:Lnk9Ch0-PP_VAL_PD2", "right": "SAROP11-PBPS110:Lnk9Ch0-PP_VAL_PD3", "left": "SAROP11-PBPS110:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 4.098127129673725e-06, - "down_calib": 4.259138962245459e-06, - "left_calib": 2.183005012581182e-06, - "right_calib": 2.4378049645254522e-06, - "horiz_calib": -4.665452678775223, - "vert_calib": -4.471600564729387, + "up_calib": 1.778951980962652e-05, + "down_calib": 1.647636031137421e-05, + "left_calib": 1.343967521143297e-05, + "right_calib": 1.4712267477026206e-05, + "horiz_calib": -4.084489404678124, + "vert_calib": -4.152037498266164, "uJ_calib": 605.4608924473305, "threshold": 0, "queue_length": 1000, @@ -69,9 +69,9 @@ 0.3 ], "calib_x_norm": [ - 0.06458675818610864, - 0.00033689669329196073, - -0.06401812569356503 + 0.07355089424549768, + 0.0005918260320667978, + -0.07334629180492924 ], "calib_y_range": [ -0.3, @@ -79,19 +79,19 @@ 0.3 ], "calib_y_norm": [ - 0.06705606264028884, - -0.00046031410996389923, - -0.0671240796408925 + 0.07059159363843578, + -0.0010492828771324209, + -0.07391577178168675 ], - "calib_datetime": "2025-03-13 13:56:21", + "calib_datetime": "2025-05-08 19:40:32", "calib_x_norm_std": [ - 0.09075244320195952, - 0.0877275386080403, - 0.0905045199152049 + 0.03951572779577543, + 0.036895048152752644, + 0.04082834764914548 ], "calib_y_norm_std": [ - 0.09006310930261463, - 0.09317196133148388, - 0.09861810436291076 + 0.042522854070151855, + 0.04157478982211735, + 0.03874863852211236 ] } \ No newline at end of file diff --git a/configuration/pipeline_config/SAROP11-PBPS122_proc.json b/configuration/pipeline_config/SAROP11-PBPS122_proc.json index 38fb650..0ad9774 100644 --- a/configuration/pipeline_config/SAROP11-PBPS122_proc.json +++ b/configuration/pipeline_config/SAROP11-PBPS122_proc.json @@ -27,12 +27,12 @@ "down": "SAROP11-PBPS122:Lnk9Ch0-PP_VAL_PD2", "right": "SAROP11-PBPS122:Lnk9Ch0-PP_VAL_PD3", "left": "SAROP11-PBPS122:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 1.893276460318213e-05, - "down_calib": 2.3306024887105614e-05, - "left_calib": 8.257188295270447e-06, - "right_calib": 8.277311386229812e-06, - "horiz_calib": -5.297540191258623, - "vert_calib": -6.29549683664401, + "up_calib": 3.136666045188568e-05, + "down_calib": 4.287710153537755e-05, + "left_calib": 1.9240676699217273e-05, + "right_calib": 1.9033978392323185e-05, + "horiz_calib": -4.800851116441316, + "vert_calib": -4.325389727023896, "uJ_calib": 605.9512700123181, "threshold": 0, "queue_length": 1000, @@ -70,14 +70,14 @@ 0.3 ], "calib_x_norm": [ - 0.056699918319259426, - -6.414005929787459e-05, - -0.05656019455162533 + 0.06260165947402832, + 0.0005469889807531498, + -0.06237617996473284 ], "calib_x_norm_std": [ - 0.09941735065928761, - 0.09644142804120188, - 0.09652404146483615 + 0.037489193261730905, + 0.04049565430616364, + 0.036369883937793064 ], "calib_y_range": [ -0.3, @@ -85,14 +85,14 @@ 0.3 ], "calib_y_norm": [ - 0.04720895487460161, - 7.316327567623546e-05, - -0.04809726408934068 + 0.0680948696951371, + -0.001074835754827735, + -0.07062095428052871 ], "calib_y_norm_std": [ - 0.09985294158981244, - 0.10707615783473319, - 0.09659571295757036 + 0.036781724002055614, + 0.03612254480714764, + 0.039495450086968996 ], - "calib_datetime": "2025-03-13 14:11:39" + "calib_datetime": "2025-05-08 19:20:54" } \ No newline at end of file diff --git a/configuration/pipeline_config/SAROP21-ATT01_proc.json b/configuration/pipeline_config/SAROP21-ATT01_proc.json index 061320b..e05bef1 100644 --- a/configuration/pipeline_config/SAROP21-ATT01_proc.json +++ b/configuration/pipeline_config/SAROP21-ATT01_proc.json @@ -29,9 +29,9 @@ "reload": true, "dark_buffer_length": 3, "calibration": [ - -7.09793884e-19, - -2.19408437e-16, - 1.2869905e-12 + -9.73024084e-19, + 3.28988958e-16, + 1.0206364e-12 ], "roi": [ 600, diff --git a/configuration/pipeline_config/SAROP21-PBPS103_proc.json b/configuration/pipeline_config/SAROP21-PBPS103_proc.json index 738bc6d..6fc3298 100644 --- a/configuration/pipeline_config/SAROP21-PBPS103_proc.json +++ b/configuration/pipeline_config/SAROP21-PBPS103_proc.json @@ -10,19 +10,19 @@ "check_timestamp": true, "stream_timeout": 20, "queue_length": 5000, - "down_calib": 3.4441678486821112e-06, + "down_calib": 1.3438944081736304e-05, "xpos_odd_w_pvname": "SAROP21-PBPS103:XPOS-ODD-HIST-W", "ypos_all_y_pvname": "SAROP21-PBPS103:YPOS-ALL-HIST-Y", "ypos_all_w_pvname": "SAROP21-PBPS103:YPOS-ALL-HIST-W", "name": "SAROP21-PBPS103_proc", - "vert_calib": -7.857205929014205, + "vert_calib": -3.9358348895956135, "bsread_address": "", "right": "SAROP21-PBPS103:Lnk9Ch0-PP_VAL_PD3", "ypos_dif_w_pvname": "SAROP21-PBPS103:YPOS-DIF-HIST-W", "ypos_odd_x_pvname": "SAROP21-PBPS103:YPOS-ODD-HIST-X", "function": "pbps_full", "port": "9009", - "left_calib": 7.724151636342986e-06, + "left_calib": 8.747777364727171e-06, "down": "SAROP21-PBPS103:Lnk9Ch0-PP_VAL_PD2", "ypos_odd_w_pvname": "SAROP21-PBPS103:YPOS-ODD-HIST-W", "xpos_odd_y_pvname": "SAROP21-PBPS103:XPOS-ODD-HIST-Y", @@ -32,7 +32,7 @@ "ypos_evn_x_pvname": "SAROP21-PBPS103:YPOS-EVN-HIST-X", "uJ_calib": 605.9512700123181, "xpos_evn_m_pvname": "SAROP21-PBPS103:XPOS-EVN-HIST-M", - "horiz_calib": -5.034588377303698, + "horiz_calib": -4.106805000082229, "ypos_all_m_pvname": "SAROP21-PBPS103:YPOS-ALL-HIST-M", "ypos_dif_m_pvname": "SAROP21-PBPS103:YPOS-DIF-HIST-M", "bsread_channels": [ @@ -45,7 +45,7 @@ "ypos_evn_w_pvname": "SAROP21-PBPS103:YPOS-EVN-HIST-W", "pipeline_type": "stream", "ypos_all_x_pvname": "SAROP21-PBPS103:YPOS-ALL-HIST-X", - "right_calib": 8.358613122862118e-06, + "right_calib": 9.684568190671639e-06, "xpos_all_m_pvname": "SAROP21-PBPS103:XPOS-ALL-HIST-M", "xpos_odd_m_pvname": "SAROP21-PBPS103:XPOS-ODD-HIST-M", "left": "SAROP21-PBPS103:Lnk9Ch0-PP_VAL_PD0", @@ -58,7 +58,7 @@ "ypos_evn_y_pvname": "SAROP21-PBPS103:YPOS-EVN-HIST-Y", "xpos_odd_x_pvname": "SAROP21-PBPS103:XPOS-ODD-HIST-X", "threshold": 0, - "up_calib": 3.381429158599253e-06, + "up_calib": 1.2578887233395516e-05, "ypos_odd_m_pvname": "SAROP21-PBPS103:YPOS-ODD-HIST-M", "xpos_all_x_pvname": "SAROP21-PBPS103:XPOS-ALL-HIST-X", "up": "SAROP21-PBPS103:Lnk9Ch0-PP_VAL_PD1", @@ -69,9 +69,9 @@ 0.3 ], "calib_x_norm": [ - 0.06233292421608874, - 0.002018002684269913, - -0.05684265778478445 + 0.07264113800909158, + -0.00027682928694833323, + -0.07345783673842782 ], "calib_y_range": [ -0.3, @@ -79,19 +79,19 @@ 0.3 ], "calib_y_norm": [ - 0.03833798078739985, - 0.0010236985633388389, - -0.03802504258511831 + 0.07681455660145628, + -0.0005021565266583652, + -0.07563086268838766 ], "calib_x_norm_std": [ - 0.6418256445167525, - 0.6759359880444116, - 0.6554307724365318 + 0.3239146627568919, + 0.3360879764341496, + 0.31769366304131247 ], "calib_y_norm_std": [ - 0.6584007080383298, - 0.6436547013306737, - 0.6454721642147889 + 0.3375259185485036, + 0.3494180218450826, + 0.34646009364333663 ], - "calib_datetime": "2025-04-04 10:40:33" + "calib_datetime": "2025-05-12 18:46:13" } \ No newline at end of file diff --git a/configuration/pipeline_config/SAROP21-PBPS133_proc.json b/configuration/pipeline_config/SAROP21-PBPS133_proc.json index af1e04e..b6ad412 100644 --- a/configuration/pipeline_config/SAROP21-PBPS133_proc.json +++ b/configuration/pipeline_config/SAROP21-PBPS133_proc.json @@ -27,12 +27,12 @@ "down": "SAROP21-PBPS133:Lnk9Ch0-PP_VAL_PD2", "right": "SAROP21-PBPS133:Lnk9Ch0-PP_VAL_PD3", "left": "SAROP21-PBPS133:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 5.349312228228193e-05, - "down_calib": 5.145953654306361e-05, - "left_calib": 3.964746111932078e-05, - "right_calib": 3.9392026612622904e-05, - "horiz_calib": -4.027132099327991, - "vert_calib": -4.3256874948249155, + "up_calib": 1.1994417318403323e-05, + "down_calib": 1.1777156969811e-05, + "left_calib": 8.546152470880694e-06, + "right_calib": 8.292371780859814e-06, + "horiz_calib": -4.030808070907288, + "vert_calib": -3.9995027435812665, "uJ_calib": 605.4608924473305, "threshold": 0, "queue_length": 3000, @@ -70,14 +70,14 @@ 0.3 ], "calib_x_norm": [ - 0.07523729980148029, - 0.00020716758487356637, - -0.07375210139052979 + 0.07558987766893042, + 0.00010780904235330975, + -0.07326364982362192 ], "calib_x_norm_std": [ - 0.3755149062751643, - 0.3786561572290592, - 0.3748798320928806 + 0.37640119241355796, + 0.3766165188476263, + 0.38834395117940335 ], "calib_y_range": [ -0.3, @@ -85,14 +85,14 @@ 0.3 ], "calib_y_norm": [ - 0.0699176899290634, - -0.00030959523730942853, - -0.06878858525096673 + 0.07489741542673554, + 0.0007133487847056664, + -0.07512123400735467 ], "calib_y_norm_std": [ - 0.37846861570249735, - 0.38072721857616276, - 0.3832545225535354 + 0.4039920352608262, + 0.4026890943163395, + 0.3902031448932646 ], - "calib_datetime": "2025-04-04 10:39:32" + "calib_datetime": "2025-05-10 10:06:36" } \ No newline at end of file diff --git a/configuration/pipeline_config/SAROP31-PBPS113_proc.json b/configuration/pipeline_config/SAROP31-PBPS113_proc.json index 62e255b..3cd7ffe 100644 --- a/configuration/pipeline_config/SAROP31-PBPS113_proc.json +++ b/configuration/pipeline_config/SAROP31-PBPS113_proc.json @@ -26,12 +26,12 @@ "down": "SAROP31-PBPS113:Lnk9Ch0-PP_VAL_PD2", "right": "SAROP31-PBPS113:Lnk9Ch0-PP_VAL_PD3", "left": "SAROP31-PBPS113:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 5.059538606264929e-07, - "down_calib": 5.64927448333758e-07, - "left_calib": 1.125846743706708e-06, - "right_calib": 1.3457517438143435e-06, - "horiz_calib": -4.719752799047021, - "vert_calib": -7.115673207163331, + "up_calib": 3.861793515770638e-05, + "down_calib": 4.4900118787754264e-05, + "left_calib": 9.126519109105711e-05, + "right_calib": 0.00011276977914264295, + "horiz_calib": -3.8084771908604007, + "vert_calib": -6.247648389472207, "uJ_calib": 941.943984588351, "threshold": 0, "queue_length": 300, @@ -70,14 +70,14 @@ 0.3 ], "calib_x_norm": [ - 0.06422228966212074, - -0.0003177089811439902, - -0.06290301234970237 + 0.0786096188018742, + 0.00035821990918893194, + -0.0789336642299587 ], "calib_x_norm_std": [ - 0.05497993675328227, - 0.05722297512373116, - 0.05244845040249846 + 0.040490407392167534, + 0.04028139405139821, + 0.03862030068928889 ], "calib_y_range": [ -0.3, @@ -85,14 +85,14 @@ 0.3 ], "calib_y_norm": [ - 0.04291975949563341, - -0.0002750343024646076, - -0.04140114487023621 + 0.04775193765978428, + -0.00032249451531026196, + -0.04828419667374328 ], "calib_y_norm_std": [ - 0.05799243003721293, - 0.05228267176725063, - 0.057094675167291845 + 0.0376808182312105, + 0.0399368891843527, + 0.037962528852795725 ], - "calib_datetime": "2025-04-03 19:26:20" + "calib_datetime": "2025-05-12 19:40:38" } \ No newline at end of file diff --git a/configuration/pipeline_config/SAROP31-PBPS149_proc.json b/configuration/pipeline_config/SAROP31-PBPS149_proc.json index d974bbc..4088534 100644 --- a/configuration/pipeline_config/SAROP31-PBPS149_proc.json +++ b/configuration/pipeline_config/SAROP31-PBPS149_proc.json @@ -27,12 +27,12 @@ "down": "SAROP31-PBPS149:Lnk9Ch0-PP_VAL_PD2", "right": "SAROP31-PBPS149:Lnk9Ch0-PP_VAL_PD3", "left": "SAROP31-PBPS149:Lnk9Ch0-PP_VAL_PD0", - "up_calib": 5.520345954869785e-07, - "down_calib": 5.619057169946393e-07, - "left_calib": 1.0781825161854172e-06, - "right_calib": 1.7591006509263466e-06, - "horiz_calib": -2.951379555975132, - "vert_calib": -5.5751155038433415, + "up_calib": 8.923922065175471e-06, + "down_calib": 9.805425067137745e-06, + "left_calib": 5.870948933687726e-06, + "right_calib": 8.378280574406873e-06, + "horiz_calib": -3.775717899566187, + "vert_calib": -3.8593992845314324, "uJ_calib": 605.9512700123181, "threshold": 0.0, "queue_length": 300, @@ -71,14 +71,14 @@ 0.3 ], "calib_x_norm": [ - 0.11031392780555577, - 0.000928620923623025, - -0.09298083270238598 + 0.08010117214752004, + 0.00011095255532506727, + -0.07880900492607362 ], "calib_x_norm_std": [ - 0.06272526898974226, - 0.06648517579759342, - 0.05664085326165932 + 0.03546822052236209, + 0.03566075428198375, + 0.038689695811515164 ], "calib_y_range": [ -0.3, @@ -86,14 +86,14 @@ 0.3 ], "calib_y_norm": [ - 0.053727058023198666, - -3.663064961326165e-05, - -0.053894030649560734 + 0.07814310330557633, + -0.0003988992296753892, + -0.07732150550150274 ], "calib_y_norm_std": [ - 0.061694021446861605, - 0.06711504500830567, - 0.06812822215629918 + 0.07331681331314215, + 0.03660437215717908, + 0.036695466427208896 ], - "calib_datetime": "2025-04-03 19:27:23" + "calib_datetime": "2025-05-12 19:42:03" } \ No newline at end of file diff --git a/configuration/pipeline_config/SATES21-CAMS-PATT1_spec_db.json b/configuration/pipeline_config/SATES21-CAMS-PATT1_spec_db.json index 72e581e..2762450 100644 --- a/configuration/pipeline_config/SATES21-CAMS-PATT1_spec_db.json +++ b/configuration/pipeline_config/SATES21-CAMS-PATT1_spec_db.json @@ -1,6 +1,6 @@ { "image_background_enable": "passive", - "image_background": "SATES21-CAMS-PATT1_20250401_153642_380292", + "image_background": "SATES21-CAMS-PATT1_20250511_094953_959660", "image_threshold": null, "image_region_of_interest": null, "image_good_region": null, @@ -20,16 +20,16 @@ "project_axis": 0, "reload": true, "roi_background": [ - 106, - 935, - 0, - 2435 + 9, + 444, + 10, + 2501 ], "roi_signal": [ - 106, - 935, - 0, - 2435 + 9, + 444, + 10, + 2501 ], "enforce_pid": true, "enforce_timestamp": true, diff --git a/configuration/pipeline_config/SATES21-CAMS154-M1_proc.json b/configuration/pipeline_config/SATES21-CAMS154-M1_proc.json new file mode 100644 index 0000000..3478813 --- /dev/null +++ b/configuration/pipeline_config/SATES21-CAMS154-M1_proc.json @@ -0,0 +1,15 @@ +{ + "image_background_enable": true, + "image_background": "SATES21-CAMS154-M1_20250430_112650_821925", + "image_threshold": null, + "image_region_of_interest": null, + "image_good_region": null, + "image_slices": null, + "mode": "PUSH", + "pipeline_type": "processing", + "function": "maloja_spectrometers.py", + "camera_name": "SATES21-CAMS154-M1", + "name": "SATES21-CAMS154-M1_proc", + "reload": true, + "block": false +} \ No newline at end of file diff --git a/configuration/pipeline_config/SATES21-CAMS154-M1_spec_db.json b/configuration/pipeline_config/SATES21-CAMS154-M1_spec_db.json index 5ca6ca1..95b12dd 100644 --- a/configuration/pipeline_config/SATES21-CAMS154-M1_spec_db.json +++ b/configuration/pipeline_config/SATES21-CAMS154-M1_spec_db.json @@ -1,12 +1,12 @@ { "image_background_enable": "passive", - "image_background": "SATES21-CAMS154-M1_20250218_150832_735370", + "image_background": "SATES21-CAMS154-M1_20250430_112650_821925", "image_threshold": null, "image_region_of_interest": null, "image_good_region": null, "image_slices": null, "pipeline_type": "processing", - "function": "spectrometer.py", + "function": "maloja_spectrometer.py", "camera_name": "SATES21-CAMS154-M1", "name": "SATES21-CAMS154-M1_spec_db", "mode": "PUSH", @@ -20,15 +20,15 @@ "threshold": 10, "project_axis": 0, "roi_signal": [ - 2, - 594, - 5, - 1412 + 1, + 847, + 1, + 1635 ], "roi_background": [ - 2, - 594, - 5, - 1412 + 1, + 847, + 1, + 1635 ] } \ No newline at end of file diff --git a/configuration/pipeline_config/SATES30-CAMS182-GIGE6_profiles.json b/configuration/pipeline_config/SATES30-CAMS182-GIGE6_profiles.json new file mode 100644 index 0000000..685a255 --- /dev/null +++ b/configuration/pipeline_config/SATES30-CAMS182-GIGE6_profiles.json @@ -0,0 +1,15 @@ +{ + "name": "SATES30-CAMS182-GIGE6_profiles", + "image_background_enable": false, + "image_background": null, + "image_threshold": null, + "image_region_of_interest": null, + "image_good_region": null, + "image_slices": null, + "pipeline_type": "processing", + "camera_name": "SATES30-CAMS182-GIGE6", + "function": "profiles", + "mode": "PUSH", + "allow_type_changes": false, + "block": false +} \ No newline at end of file diff --git a/configuration/pipeline_config/SATES30-CAMS182-GIGE6_sp.json b/configuration/pipeline_config/SATES30-CAMS182-GIGE6_sp.json new file mode 100644 index 0000000..f5bd2f9 --- /dev/null +++ b/configuration/pipeline_config/SATES30-CAMS182-GIGE6_sp.json @@ -0,0 +1,12 @@ +{ + "image_background_enable": false, + "image_background": null, + "image_threshold": null, + "image_region_of_interest": null, + "image_good_region": null, + "image_slices": null, + "pipeline_type": "processing", + "camera_name": "SATES30-CAMS182-GIGE6", + "name": "SATES30-CAMS182-GIGE6_sp", + "max_frame_rate": 5.1 +} \ No newline at end of file diff --git a/configuration/pipeline_config/SATES30-CAMS182-GIGE_sp.json b/configuration/pipeline_config/SATES30-CAMS182-GIGE_sp.json new file mode 100644 index 0000000..47f814d --- /dev/null +++ b/configuration/pipeline_config/SATES30-CAMS182-GIGE_sp.json @@ -0,0 +1,11 @@ +{ + "image_background_enable": false, + "image_background": null, + "image_threshold": null, + "image_region_of_interest": null, + "image_good_region": null, + "image_slices": null, + "pipeline_type": "processing", + "camera_name": "SATES30-CAMS182-GIGE", + "name": "SATES30-CAMS182-GIGE_sp" +} \ No newline at end of file diff --git a/configuration/pipeline_config/SATES31-CAMS187-RIXS1_sp.json b/configuration/pipeline_config/SATES31-CAMS187-RIXS1_sp.json index 728af2f..4602dfd 100644 --- a/configuration/pipeline_config/SATES31-CAMS187-RIXS1_sp.json +++ b/configuration/pipeline_config/SATES31-CAMS187-RIXS1_sp.json @@ -1,6 +1,6 @@ { - "image_background_enable": false, - "image_background": "SATES31-CAMS187-RIXS1_20250222_100737_996974", + "image_background_enable": true, + "image_background": "SATES31-CAMS187-RIXS1_20250513_091423_890325", "image_threshold": null, "image_region_of_interest": null, "image_good_region": null, @@ -9,5 +9,6 @@ "camera_name": "SATES31-CAMS187-RIXS1", "name": "SATES31-CAMS187-RIXS1_sp", "max_frame_rate": 5.1, - "reload": true + "reload": true, + "_function": "Bernina_mid_IR_CEP_analysis.py" } \ No newline at end of file diff --git a/configuration/pipeline_config/permanent_instances.json b/configuration/pipeline_config/permanent_instances.json index 1968ea7..306a2fe 100644 --- a/configuration/pipeline_config/permanent_instances.json +++ b/configuration/pipeline_config/permanent_instances.json @@ -1,9 +1,9 @@ { "#Bernina_tt_kb_restart_cam": "Bernina_tt_kb_restart_cam", "#S10BC02-DSRM310_profiles": "S10BC02-DSRM310_profiles", - "#SARES11-SPEC125-M1_psen_db": "SARES11-SPEC125-M1_psen_db", "#SARES11-SPEC125-M1_test": "SARES11-SPEC125-M1_test", "#SARES11-SPEC125-M3_sp": "SARES11-SPEC125-M3_sp", + "#SARES11-SPEC125-M3_spec_db": "SARES11-SPEC125-M3_spec_db", "#SARES11-XMI125-C4P1_db": "SARES11-XMI125-C4P1_db", "#SARES11-XMI125-C4P1_xy_db": "SARES11-XMI125-C4P1_xy_db", "#SARES12-CAMS128-M1_psen_db": "SARES12-CAMS128-M1_psen_db", @@ -65,8 +65,8 @@ "SARBD02-DSCR051_sp": "SARBD02-DSCR051_sp", "SARBD02-DSCR05x_merge": "SARBD02-DSCR05x_merge", "SARCL01-DSCR170_profiles": "SARCL01-DSCR170_profiles", + "SARES11-SPEC125-M1_psen_db": "SARES11-SPEC125-M1_psen_db", "SARES11-SPEC125-M2_db": "SARES11-SPEC125-M2_db", - "SARES11-SPEC125-M3_spec_db": "SARES11-SPEC125-M3_spec_db", "SARES20-CAMS142-M3_proc": "SARES20-CAMS142-M3_proc", "SARES20-CAMS142-M4_psen_db": "SARES20-CAMS142-M4_psen_db", "SARES20-CAMS142-M5_psen_db": "SARES20-CAMS142-M5_psen_db", @@ -99,6 +99,7 @@ "SATBD02-DSCR050_sp1": "SATBD02-DSCR050_sp_rep", "SATES21-CAMS-PATT1_spec_db": "SATES21-CAMS-PATT1_spec_db", "SATES21-CAMS154-GIGE9_proc": "SATES21-CAMS154-GIGE9_proc", + "SATES21-CAMS154-M1_proc": "SATES21-CAMS154-M1_proc", "SATES21-CAMS154-M1_spec_db": "SATES21-CAMS154-M1_spec_db", "SATES21-CAMS154-M2_proc": "SATES21-CAMS154-M2_proc", "SATES22-ADTEST1-CAM10_proc": "SATES22-ADTEST1-CAM10_proc", @@ -115,6 +116,7 @@ "SATES22-ADTEST1-CAM9_proc": "SATES22-ADTEST1-CAM9_proc", "SATES24-CAMS161-M1_spec_db": "SATES24-CAMS161-M1_spec_db", "SATES30-CAMS182-GIGE1_profiles": "SATES30-CAMS182-GIGE1_profiles", + "SATES30-CAMS182-GIGE6_profiles": "SATES30-CAMS182-GIGE6_profiles", "SATES30-RIXS-CAM01_fit": "SATES30-RIXS-CAM01_fit", "SATES30-RIXS-CAM01_proc": "SATES30-RIXS-CAM01_proc", "SATES30-RIXS-CAM01_repeater": "SATES30-RIXS-CAM01_repeater", diff --git a/configuration/pipeline_config/servers.json b/configuration/pipeline_config/servers.json index eb780fc..48574d0 100644 --- a/configuration/pipeline_config/servers.json +++ b/configuration/pipeline_config/servers.json @@ -96,7 +96,8 @@ "SLG-LCAM-C071", "SARBD01-DSCR050", "SARBD02-DSCR050", - "SARBD02-DSCR051" + "SARBD02-DSCR051", + "S10DI01-DSCR020" ], "enabled": true, "expanding": false, @@ -136,7 +137,7 @@ "instances": [ "SARFE10-PSSS059_psss:8889", "SARFE10-PSSS059_sp", - "SARFE10-PSSS059_psss_avg:9005", + "#SARFE10-PSSS059_psss_avg:9005", "SARFE10-PSSS059_store:8890", "SARFE10-PSSS059-LB_psss:9004" ] @@ -178,7 +179,8 @@ "#SATES21-CAMS154-GIGE8_proc:9058", "#SATES21-CAMS154-GIGE9_proc", "SATES21-CAMS154-GIGE10_sp", - "SATES21-CAMS154-GIGE11_sp" + "SATES21-CAMS154-GIGE11_sp", + "SATES21-CAMS154-M1_proc" ] }, "http://sf-daqsync-13.psi.ch:8881": { @@ -198,7 +200,7 @@ "expanding": false, "instances": [ "#SAROP11-PBPS122_proc:9010", - "#SARES11-SPEC125-M1_psen_db:9001", + "SARES11-SPEC125-M1_psen_db:9001", "#SARES11-SPEC125-M1_test:9009", "#SARES11-SPEC125-M2_psen_db:9011", "#SARES12-CAMS128-M1_psen_db:9003", @@ -277,6 +279,7 @@ "SATES30-CAMS182-GIGE3", "SATES30-CAMS182-GIGE4", "SATES30-CAMS182-GIGE5", + "SATES30-CAMS182-GIGE6", "SATES30-RIXS-CAM01", "furka_jungfrau" ], @@ -295,6 +298,7 @@ "SATES30-CAMS182-GIGE2_sp", "SATES30-CAMS182-GIGE1_profiles:9002", "SATES30-CAMS182-GIGE2_profiles:9004", + "SATES30-CAMS182-GIGE6_profiles:9008", "test_furka_proc:9020" ] }, diff --git a/configuration/user_scripts/SARES11-SPEC125-M1_2tt.py b/configuration/user_scripts/SARES11-SPEC125-M1_2tt.py index aa5c889..46c6d2a 100644 --- a/configuration/user_scripts/SARES11-SPEC125-M1_2tt.py +++ b/configuration/user_scripts/SARES11-SPEC125-M1_2tt.py @@ -79,7 +79,7 @@ def edge(filter_name, backgrounds, signals, peakback): sig_deriv -= peakback sig_deriv *= signal.tukey(2048) # just added Nov 20 2024 #peak_pos = 1024 - (np.argmax(sig_deriv[500:1500], axis=-1) + 500) - peak_pos = 800 - (np.argmax(sig_deriv[400:1600], axis=-1) + 400) # I am grossed out + peak_pos = 1024 - (np.argmax(sig_deriv[400:1600], axis=-1) + 400) # I am grossed out peak_amp = np.amax(sig_deriv[400:1600], axis=-1) return peak_pos, peak_amp, sig_deriv, sig_uninterp diff --git a/configuration/user_scripts/swissfel_spectral_processing.py b/configuration/user_scripts/swissfel_spectral_processing.py index 8aa6e5a..8eb4e27 100644 --- a/configuration/user_scripts/swissfel_spectral_processing.py +++ b/configuration/user_scripts/swissfel_spectral_processing.py @@ -25,6 +25,12 @@ base_pv_names = [] all_pv_names = [] global_ravg_length = 100 ravg_buffers = {} +# Configuration for rolling-average of statistics + +# Configuration for N-shot average spectrum +global_avg_length = 100 +avg_buffer = None +avg_pv_names = [] @numba.njit(parallel=False) def get_spectrum(image, background): @@ -55,51 +61,66 @@ def update_PVs(buffer, *pv_names): def initialize(params): - """Initialize PV names, running-average settings, and launch update thread.""" + """Initialize PV names, running-average settings, N-shot average, and launch update thread.""" global channel_pv_names, base_pv_names, all_pv_names, global_ravg_length + global global_avg_length, avg_buffer, avg_pv_names camera = params["camera_name"] e_int = params["e_int_name"] e_axis = params["e_axis_name"] # Fit/result PV names - center_pv = f"{camera}:FIT-COM" - fwhm_pv = f"{camera}:FIT-FWHM" - fit_rms_pv = f"{camera}:FIT-RMS" - fit_res_pv = f"{camera}:FIT-RES" + center_pv = f"{camera}:FIT-COM" + fwhm_pv = f"{camera}:FIT-FWHM" + fit_rms_pv = f"{camera}:FIT-RMS" + fit_res_pv = f"{camera}:FIT-RES" + fit_spec_pv = f"{camera}:FIT-SPECTRUM_Y" # ROI PVs for dynamic read - ymin_pv = f"{camera}:SPC_ROI_YMIN" - ymax_pv = f"{camera}:SPC_ROI_YMAX" - axis_pv = e_axis + ymin_pv = f"{camera}:SPC_ROI_YMIN" + ymax_pv = f"{camera}:SPC_ROI_YMAX" + axis_pv = e_axis channel_pv_names = [ymin_pv, ymax_pv, axis_pv] # Spectrum statistical PV names - com_pv = f"{camera}:SPECT-COM" - std_pv = f"{camera}:SPECT-RMS" - skew_pv = f"{camera}:SPECT-SKEW" - iqr_pv = f"{camera}:SPECT-IQR" - res_pv = f"{camera}:SPECT-RES" # will use IQR-based calc + com_pv = f"{camera}:SPECT-COM" + std_pv = f"{camera}:SPECT-RMS" + skew_pv = f"{camera}:SPECT-SKEW" + iqr_pv = f"{camera}:SPECT-IQR" + res_pv = f"{camera}:SPECT-RES" # Base PVs for update thread (order matters) base_pv_names = [ e_int, center_pv, fwhm_pv, fit_rms_pv, - fit_res_pv, com_pv, std_pv, skew_pv, iqr_pv, res_pv + fit_res_pv, fit_spec_pv, com_pv, std_pv, skew_pv, iqr_pv, res_pv ] # Running-average configuration global_ravg_length = params.get('RAVG_length', global_ravg_length) - # Build list of running-average PVs (exclude e_int, e_axis, processing_parameters) - exclude = { - e_int, - e_axis, - f"{camera}:processing_parameters" - } + exclude = {e_int, e_axis, f"{camera}:processing_parameters"} ravg_base = [pv for pv in base_pv_names if pv not in exclude] ravg_pv_names = [pv + '-RAVG' for pv in ravg_base] - # All PVs (original + running average) - all_pv_names = base_pv_names + ravg_pv_names + # N-shot average configuration + global_avg_length = params.get('avg_nshots', global_avg_length) + avg_buffer = deque(maxlen=global_avg_length) + # Define PVs for N-shot average statistics and spectra + avg_pv_names = [ + f"{camera}:AVG-FIT-COM", + f"{camera}:AVG-FIT-FWHM", + f"{camera}:AVG-FIT-RMS", + f"{camera}:AVG-FIT-RES", + f"{camera}:AVG-SPECT-COM", + f"{camera}:AVG-SPECT-RMS", + f"{camera}:AVG-SPECT-SKEW", + f"{camera}:AVG-SPECT-IQR", + f"{camera}:AVG-SPECT-RES", + f"{camera}:AVG-SPECTRUM_Y", + f"{camera}:AVG-FIT-SPECTRUM_Y" + ] + + # All PVs (original + running average + N-shot average) + all_pv_names = base_pv_names + ravg_pv_names + avg_pv_names # Start background thread for PV updates thread = Thread(target=update_PVs, args=(buffer, *all_pv_names), daemon=True) @@ -109,15 +130,18 @@ def initialize(params): def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata=None, background=None): """ Main entrypoint: subtract background, crop ROI, smooth, fit Gaussian, - compute metrics, queue PV updates (with running averages for skew and IQR). - Returns a dict of processed PV values (original channels only). + compute metrics, N-shot average, queue PV updates (with running averages). + Returns a dict of processed PV values (original and average channels). """ - global initialized, sent_pid, channel_pv_names, global_ravg_length, ravg_buffers + global initialized, sent_pid, channel_pv_names + global global_ravg_length, ravg_buffers, avg_buffer try: if not initialized: initialize(parameters) initialized = True + camera = parameters["camera_name"] + # Dynamic ROI and axis PV read ymin_pv, ymax_pv, axis_pv = create_thread_pvs(channel_pv_names) if ymin_pv and ymin_pv.connected: @@ -128,11 +152,7 @@ def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata if not (axis_pv and axis_pv.connected): _logger.warning("Energy axis not connected") return None - axis = axis_pv.value - if len(axis) < image.shape[1]: - _logger.warning("Energy axis length %d < image width %d", len(axis), image.shape[1]) - return None - axis = axis[:image.shape[1]] + axis = axis_pv.value[:image.shape[1]] # Preprocess image proc_img = image.astype(np.float32) - np.float32(parameters.get("pixel_bkg", 0)) @@ -140,51 +160,36 @@ def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata # Background image bg_img = parameters.pop('background_data', None) - if not (isinstance(bg_img, np.ndarray) and bg_img.shape == proc_img.shape): - bg_img = None - else: - bg_img = bg_img.astype(np.float32) + bg_img = bg_img.astype(np.float32) if isinstance(bg_img, np.ndarray) and bg_img.shape == proc_img.shape else None # Crop ROI - ymin, ymax = int(global_roi[0]), int(global_roi[1]) + ymin, ymax = map(int, global_roi) if 0 <= ymin < ymax <= nrows: proc_img = proc_img[ymin:ymax, :] if bg_img is not None: bg_img = bg_img[ymin:ymax, :] - # Extract spectrum + # Extract spectrum and fitted spectrum spectrum = get_spectrum(proc_img, bg_img) if bg_img is not None else np.sum(proc_img, axis=0) - - # Smooth smoothed = scipy.signal.savgol_filter(spectrum, 51, 3) - - # Noise check and fit Gaussian minimum, maximum = smoothed.min(), smoothed.max() amplitude = maximum - minimum skip = amplitude <= nrows * 1.5 offset, amp_fit, center, sigma = functions.gauss_fit_psss( - smoothed[::2], axis[::2], offset=minimum, - amplitude=amplitude, skip=skip, maxfev=10 + smoothed[::2], axis[::2], offset=minimum, amplitude=amplitude, skip=skip, maxfev=10 ) + # Reconstruct fitted curve + fit_spectrum = offset + amp_fit * np.exp(-((axis - center)**2) / (2 * sigma**2)) - # Compute normalized spectrum weights + # Moments sm_norm = smoothed / np.sum(smoothed) - - # Statistical moments - spect_com = np.sum(axis * sm_norm) - spect_std = np.sqrt(np.sum((axis - spect_com)**2 * sm_norm)) + spect_com = np.sum(axis * sm_norm) + spect_std = np.sqrt(np.sum((axis - spect_com)**2 * sm_norm)) spect_skew = np.sum((axis - spect_com)**3 * sm_norm) / (spect_std**3) + cum = np.cumsum(sm_norm); e25 = np.interp(0.25, cum, axis); e75 = np.interp(0.75, cum, axis) + spect_iqr = e75 - e25; spect_sum = np.sum(spectrum) - # Interquartile width (IQR) - cum = np.cumsum(sm_norm) - e25 = np.interp(0.25, cum, axis) - e75 = np.interp(0.75, cum, axis) - spect_iqr = e75 - e25 - - spect_sum = np.sum(spectrum) - - camera = parameters["camera_name"] - # Original result dict + # Original result result = { parameters["e_int_name"]: spectrum, parameters["e_axis_name"]: axis, @@ -193,31 +198,59 @@ def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata f"{camera}:FIT-FWHM": np.float64(2.355 * sigma), f"{camera}:FIT-RMS": np.float64(sigma), f"{camera}:FIT-RES": np.float64(2.355 * sigma / center * 1000), + f"{camera}:FIT-SPECTRUM_Y": fit_spectrum, f"{camera}:SPECT-COM": spect_com, f"{camera}:SPECT-RMS": spect_std, f"{camera}:SPECT-SKEW": spect_skew, f"{camera}:SPECT-IQR": spect_iqr, - # Use IQR for relative spread instead of std f"{camera}:SPECT-RES": np.float64(spect_iqr / spect_com * 1000), f"{camera}:processing_parameters": json.dumps({"roi": global_roi}) } - # Prepare full values for PV update (including running averages) - exclude = { - parameters["e_int_name"], - parameters["e_axis_name"], - f"{camera}:processing_parameters" - } + # Rolling averages + exclude = {parameters["e_int_name"], parameters["e_axis_name"], f"{camera}:processing_parameters"} ravg_results = {} for base_pv in (pv for pv in base_pv_names if pv not in exclude): buf = ravg_buffers.setdefault(base_pv, deque(maxlen=global_ravg_length)) - buf.append(result.get(base_pv)) + buf.append(result[base_pv]) ravg_results[f"{base_pv}-RAVG"] = np.mean(buf) - # Merge for PV write - full_results = {**result, **ravg_results} + # N-shot average and average fitted spectrum + avg_buffer.append(spectrum) + avg_spectrum = np.mean(np.stack(avg_buffer), axis=0) + fit_avg = offset + amp_fit * np.exp(-((axis - center)**2) / (2 * sigma**2)) # using avg fit params below + sm_avg = scipy.signal.savgol_filter(avg_spectrum, 51, 3) + min_a, max_a = sm_avg.min(), sm_avg.max() + amp_a = max_a - min_a; skip_a = amp_a <= nrows * 1.5 + offs_a, amp_fit_a, center_a, sigma_a = functions.gauss_fit_psss( + sm_avg[::2], axis[::2], offset=min_a, amplitude=amp_a, skip=skip_a, maxfev=10 + ) + fit_avg_spectrum = offs_a + amp_fit_a * np.exp(-((axis - center_a)**2) / (2 * sigma_a**2)) + # Average moments + sm_norm_a = sm_avg / np.sum(sm_avg) + spect_com_a = np.sum(axis * sm_norm_a) + spect_std_a = np.sqrt(np.sum((axis - spect_com_a)**2 * sm_norm_a)) + spect_skew_a = np.sum((axis - spect_com_a)**3 * sm_norm_a) / (spect_std_a**3) + cum_a = np.cumsum(sm_norm_a); e25_a = np.interp(0.25, cum_a, axis); e75_a = np.interp(0.75, cum_a, axis) + spect_iqr_a = e75_a - e25_a + spect_res_a = spect_iqr_a / spect_com_a * 1000 - # Queue PV update if new pulse + avg_results = { + f"{camera}:AVG-FIT-COM": np.float64(center_a), + f"{camera}:AVG-FIT-FWHM": np.float64(2.355 * sigma_a), + f"{camera}:AVG-FIT-RMS": np.float64(sigma_a), + f"{camera}:AVG-FIT-RES": np.float64(2.355 * sigma_a / center_a * 1000), + f"{camera}:AVG-SPECT-COM": spect_com_a, + f"{camera}:AVG-SPECT-RMS": spect_std_a, + f"{camera}:AVG-SPECT-SKEW": spect_skew_a, + f"{camera}:AVG-SPECT-IQR": spect_iqr_a, + f"{camera}:AVG-SPECT-RES": np.float64(spect_res_a), + f"{camera}:AVG-SPECTRUM_Y": avg_spectrum, + f"{camera}:AVG-FIT-SPECTRUM_Y": fit_avg_spectrum + } + + # Merge and queue + full_results = {**result, **ravg_results, **avg_results} if epics_lock.acquire(False): try: if pulse_id > sent_pid: diff --git a/configuration/user_scripts/swissfel_spectral_processing_bkg.py b/configuration/user_scripts/swissfel_spectral_processing_bkg.py new file mode 100644 index 0000000..8aa6e5a --- /dev/null +++ b/configuration/user_scripts/swissfel_spectral_processing_bkg.py @@ -0,0 +1,234 @@ +from logging import getLogger +from cam_server.pipeline.data_processing import functions +from cam_server.utils import create_thread_pvs, epics_lock +from collections import deque +import json +import numpy as np +import scipy.signal +import numba +import time +import sys +from threading import Thread + +# Configure Numba to use multiple threads +numba.set_num_threads(4) + +_logger = getLogger(__name__) + +# Shared state globals +global_roi = [0, 0] +initialized = False +sent_pid = -1 +buffer = deque(maxlen=5) +channel_pv_names = None +base_pv_names = [] +all_pv_names = [] +global_ravg_length = 100 +ravg_buffers = {} + +@numba.njit(parallel=False) +def get_spectrum(image, background): + """Compute background-subtracted spectrum via row-wise summation.""" + y, x = image.shape + profile = np.zeros(x, dtype=np.float64) + for i in numba.prange(y): + for j in range(x): + profile[j] += image[i, j] - background[i, j] + return profile + + +def update_PVs(buffer, *pv_names): + """Continuously read from buffer and write to EPICS PVs.""" + pvs = create_thread_pvs(list(pv_names)) + while True: + time.sleep(0.1) + try: + rec = buffer.popleft() + except IndexError: + continue + try: + for pv, val in zip(pvs, rec): + if pv and pv.connected and (val is not None): + pv.put(val) + except Exception: + _logger.exception("Error updating channels") + + +def initialize(params): + """Initialize PV names, running-average settings, and launch update thread.""" + global channel_pv_names, base_pv_names, all_pv_names, global_ravg_length + + camera = params["camera_name"] + e_int = params["e_int_name"] + e_axis = params["e_axis_name"] + + # Fit/result PV names + center_pv = f"{camera}:FIT-COM" + fwhm_pv = f"{camera}:FIT-FWHM" + fit_rms_pv = f"{camera}:FIT-RMS" + fit_res_pv = f"{camera}:FIT-RES" + + # ROI PVs for dynamic read + ymin_pv = f"{camera}:SPC_ROI_YMIN" + ymax_pv = f"{camera}:SPC_ROI_YMAX" + axis_pv = e_axis + channel_pv_names = [ymin_pv, ymax_pv, axis_pv] + + # Spectrum statistical PV names + com_pv = f"{camera}:SPECT-COM" + std_pv = f"{camera}:SPECT-RMS" + skew_pv = f"{camera}:SPECT-SKEW" + iqr_pv = f"{camera}:SPECT-IQR" + res_pv = f"{camera}:SPECT-RES" # will use IQR-based calc + + # Base PVs for update thread (order matters) + base_pv_names = [ + e_int, center_pv, fwhm_pv, fit_rms_pv, + fit_res_pv, com_pv, std_pv, skew_pv, iqr_pv, res_pv + ] + + # Running-average configuration + global_ravg_length = params.get('RAVG_length', global_ravg_length) + # Build list of running-average PVs (exclude e_int, e_axis, processing_parameters) + exclude = { + e_int, + e_axis, + f"{camera}:processing_parameters" + } + ravg_base = [pv for pv in base_pv_names if pv not in exclude] + ravg_pv_names = [pv + '-RAVG' for pv in ravg_base] + + # All PVs (original + running average) + all_pv_names = base_pv_names + ravg_pv_names + + # Start background thread for PV updates + thread = Thread(target=update_PVs, args=(buffer, *all_pv_names), daemon=True) + thread.start() + + +def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata=None, background=None): + """ + Main entrypoint: subtract background, crop ROI, smooth, fit Gaussian, + compute metrics, queue PV updates (with running averages for skew and IQR). + Returns a dict of processed PV values (original channels only). + """ + global initialized, sent_pid, channel_pv_names, global_ravg_length, ravg_buffers + try: + if not initialized: + initialize(parameters) + initialized = True + + # Dynamic ROI and axis PV read + ymin_pv, ymax_pv, axis_pv = create_thread_pvs(channel_pv_names) + if ymin_pv and ymin_pv.connected: + global_roi[0] = ymin_pv.value + if ymax_pv and ymax_pv.connected: + global_roi[1] = ymax_pv.value + + if not (axis_pv and axis_pv.connected): + _logger.warning("Energy axis not connected") + return None + axis = axis_pv.value + if len(axis) < image.shape[1]: + _logger.warning("Energy axis length %d < image width %d", len(axis), image.shape[1]) + return None + axis = axis[:image.shape[1]] + + # Preprocess image + proc_img = image.astype(np.float32) - np.float32(parameters.get("pixel_bkg", 0)) + nrows, _ = proc_img.shape + + # Background image + bg_img = parameters.pop('background_data', None) + if not (isinstance(bg_img, np.ndarray) and bg_img.shape == proc_img.shape): + bg_img = None + else: + bg_img = bg_img.astype(np.float32) + + # Crop ROI + ymin, ymax = int(global_roi[0]), int(global_roi[1]) + if 0 <= ymin < ymax <= nrows: + proc_img = proc_img[ymin:ymax, :] + if bg_img is not None: + bg_img = bg_img[ymin:ymax, :] + + # Extract spectrum + spectrum = get_spectrum(proc_img, bg_img) if bg_img is not None else np.sum(proc_img, axis=0) + + # Smooth + smoothed = scipy.signal.savgol_filter(spectrum, 51, 3) + + # Noise check and fit Gaussian + minimum, maximum = smoothed.min(), smoothed.max() + amplitude = maximum - minimum + skip = amplitude <= nrows * 1.5 + offset, amp_fit, center, sigma = functions.gauss_fit_psss( + smoothed[::2], axis[::2], offset=minimum, + amplitude=amplitude, skip=skip, maxfev=10 + ) + + # Compute normalized spectrum weights + sm_norm = smoothed / np.sum(smoothed) + + # Statistical moments + spect_com = np.sum(axis * sm_norm) + spect_std = np.sqrt(np.sum((axis - spect_com)**2 * sm_norm)) + spect_skew = np.sum((axis - spect_com)**3 * sm_norm) / (spect_std**3) + + # Interquartile width (IQR) + cum = np.cumsum(sm_norm) + e25 = np.interp(0.25, cum, axis) + e75 = np.interp(0.75, cum, axis) + spect_iqr = e75 - e25 + + spect_sum = np.sum(spectrum) + + camera = parameters["camera_name"] + # Original result dict + result = { + parameters["e_int_name"]: spectrum, + parameters["e_axis_name"]: axis, + f"{camera}:SPECTRUM_Y_SUM": spect_sum, + f"{camera}:FIT-COM": np.float64(center), + f"{camera}:FIT-FWHM": np.float64(2.355 * sigma), + f"{camera}:FIT-RMS": np.float64(sigma), + f"{camera}:FIT-RES": np.float64(2.355 * sigma / center * 1000), + f"{camera}:SPECT-COM": spect_com, + f"{camera}:SPECT-RMS": spect_std, + f"{camera}:SPECT-SKEW": spect_skew, + f"{camera}:SPECT-IQR": spect_iqr, + # Use IQR for relative spread instead of std + f"{camera}:SPECT-RES": np.float64(spect_iqr / spect_com * 1000), + f"{camera}:processing_parameters": json.dumps({"roi": global_roi}) + } + + # Prepare full values for PV update (including running averages) + exclude = { + parameters["e_int_name"], + parameters["e_axis_name"], + f"{camera}:processing_parameters" + } + ravg_results = {} + for base_pv in (pv for pv in base_pv_names if pv not in exclude): + buf = ravg_buffers.setdefault(base_pv, deque(maxlen=global_ravg_length)) + buf.append(result.get(base_pv)) + ravg_results[f"{base_pv}-RAVG"] = np.mean(buf) + + # Merge for PV write + full_results = {**result, **ravg_results} + + # Queue PV update if new pulse + if epics_lock.acquire(False): + try: + if pulse_id > sent_pid: + sent_pid = pulse_id + entry = tuple(full_results.get(pv) for pv in all_pv_names) + buffer.append(entry) + finally: + epics_lock.release() + + return full_results + + except Exception as ex: + _logger.warning("Processing error: %s", ex) + return {} diff --git a/configuration/user_scripts/swissfel_spectral_processing_fast.py b/configuration/user_scripts/swissfel_spectral_processing_fast.py new file mode 100644 index 0000000..34c3a3d --- /dev/null +++ b/configuration/user_scripts/swissfel_spectral_processing_fast.py @@ -0,0 +1,283 @@ +from logging import getLogger +from cam_server.pipeline.data_processing import functions +from cam_server.utils import epics_lock, create_thread_pvs +from collections import deque +import threading +import json +import numpy as np +import scipy.signal +import numba +import time +from threading import Thread + +# Configure Numba to use multiple threads and parallelize +numba.set_num_threads(4) + +_logger = getLogger(__name__) + +# Shared state globals +global_roi = [0, 0] +last_roi = None +params_json = None +initialized = False +sent_pid = -1 +buffer = deque(maxlen=5) +channel_pv_names = [] +base_pv_names = [] +all_pv_names = [] +global_ravg_length = 100 +# Running-average state +global ravg_buffers, ravg_sum_map, ravg_lock +ravg_buffers = {} +ravg_sum_map = {} +ravg_lock = threading.Lock() +# N-shot average state +global global_avg_n, avg_buffer, avg_sum +global_avg_n = 100 +avg_buffer = deque() +avg_sum = None +# Cached PV handles +pv_handles = None + +# Update thread function +def update_PVs(): + """Continuously pop and write to EPICS PVs using cached handles.""" + global pv_handles, buffer + while True: + try: + entry = buffer.popleft() + except IndexError: + time.sleep(0.001) + continue + for pv, val in zip(pv_handles, entry): + if pv and pv.connected and val is not None: + pv.put(val) + + +def initialize(params): + """Initialize PV names, caches, and start update thread.""" + global channel_pv_names, base_pv_names, all_pv_names + global global_ravg_length, global_avg_n, avg_buffer, avg_sum, pv_handles + + camera = params["camera_name"] + e_int = params["e_int_name"] + e_axis = params["e_axis_name"] + + # Fit/result PV names + center_pv = f"{camera}:FIT-COM" + fwhm_pv = f"{camera}:FIT-FWHM" + fit_rms_pv = f"{camera}:FIT-RMS" + fit_res_pv = f"{camera}:FIT-RES" + fit_spec_pv = f"{camera}:FIT-SPECTRUM_Y" + + # ROI PVs + ymin_pv = f"{camera}:SPC_ROI_YMIN" + ymax_pv = f"{camera}:SPC_ROI_YMAX" + channel_pv_names = [ymin_pv, ymax_pv, e_axis] + + # Stats PVs + com_pv = f"{camera}:SPECT-COM" + std_pv = f"{camera}:SPECT-RMS" + skew_pv = f"{camera}:SPECT-SKEW" + iqr_pv = f"{camera}:SPECT-IQR" + res_pv = f"{camera}:SPECT-RES" + + # Base PV list + base_pv_names = [ + e_int, center_pv, fwhm_pv, fit_rms_pv, + fit_res_pv, fit_spec_pv, com_pv, std_pv, skew_pv, iqr_pv, res_pv + ] + + # Running-average PV names + global_ravg_length = params.get('RAVG_length', global_ravg_length) + exclude = {e_int, e_axis, f"{camera}:processing_parameters"} + ravg_base = [pv for pv in base_pv_names if pv not in exclude] + ravg_pv_names = [pv + '-RAVG' for pv in ravg_base] + + # N-shot average settings + global_avg_n = params.get('avg_nshots', global_avg_n) + avg_buffer = deque(maxlen=global_avg_n) + avg_sum = None + avg_pv_names = [ + f"{camera}:AVG-FIT-COM", + f"{camera}:AVG-FIT-FWHM", + f"{camera}:AVG-FIT-RMS", + f"{camera}:AVG-FIT-RES", + f"{camera}:AVG-SPECT-COM", + f"{camera}:AVG-SPECT-RMS", + f"{camera}:AVG-SPECT-SKEW", + f"{camera}:AVG-SPECT-IQR", + f"{camera}:AVG-SPECT-RES", + f"{camera}:AVG-SPECTRUM_Y", + f"{camera}:AVG-FIT-SPECTRUM_Y" + ] + + # All PVs + all_pv_names = base_pv_names + ravg_pv_names + avg_pv_names + [f"{camera}:processing_parameters"] + + # Cache PV handles + pv_handles = create_thread_pvs(all_pv_names) + + # Start update thread + thread = Thread(target=update_PVs, daemon=True) + thread.start() + + +def process_image(image, pulse_id, timestamp, x_axis, y_axis, parameters, bsdata=None, background=None): + """ + Fast processing: vectorized spectrum, incremental averages, cached PVs. + """ + global initialized, sent_pid, buffer + global global_roi, last_roi, params_json + global ravg_buffers, ravg_sum_map, ravg_lock + global avg_buffer, avg_sum, global_avg_n, all_pv_names + + if not initialized: + initialize(parameters) + initialized = True + + camera = parameters["camera_name"] + + # Read ROI once and detect change + ymin, ymax = global_roi + new_ymin = parameters.get('roi_ymin', ymin) + new_ymax = parameters.get('roi_ymax', ymax) + if (new_ymin, new_ymax) != (ymin, ymax): + global_roi[:] = [new_ymin, new_ymax] + roi_changed = True + else: + roi_changed = False + + # Preprocess image + img = image.astype(np.float32) + pixel_bkg = parameters.get("pixel_bkg", 0) + proc_img = img - pixel_bkg + nrows, _ = proc_img.shape + + # Background subtraction + bg = parameters.pop('background_data', None) + if isinstance(bg, np.ndarray) and bg.shape == proc_img.shape: + proc_img -= bg.astype(np.float32) + + # Crop ROI + ymin_i, ymax_i = int(global_roi[0]), int(global_roi[1]) + if 0 <= ymin_i < ymax_i <= nrows: + proc_img = proc_img[ymin_i:ymax_i, :] + + # Spectrum via vector sum + spectrum = np.sum(proc_img, axis=0) + # Smooth + smoothed = scipy.signal.savgol_filter(spectrum, 51, 3) + # Fit Gaussian + minimum, maximum = smoothed.min(), smoothed.max() + amplitude = maximum - minimum + skip = amplitude <= nrows * 1.5 + offset, amp_fit, center, sigma = functions.gauss_fit_psss( + smoothed[::2], x_axis[:len(smoothed)][::2], offset=minimum, + amplitude=amplitude, skip=skip, maxfev=10 + ) + fit_spectrum = offset + amp_fit * np.exp(-((x_axis[:len(smoothed)] - center)**2) / (2 * sigma**2)) + + # Compute stats + sm_norm = smoothed / np.sum(smoothed) + spect_com = np.dot(x_axis[:len(sm_norm)], sm_norm) + spect_std = np.sqrt(np.dot((x_axis[:len(sm_norm)] - spect_com)**2, sm_norm)) + spect_skew = np.dot((x_axis[:len(sm_norm)] - spect_com)**3, sm_norm) / (spect_std**3) + cum = np.cumsum(sm_norm) + e25 = np.interp(0.25, cum, x_axis[:len(cum)]) + e75 = np.interp(0.75, cum, x_axis[:len(cum)]) + spect_iqr = e75 - e25 + spect_sum = spectrum.sum() + + # Original result + result = { + parameters["e_int_name"]: spectrum, + parameters["e_axis_name"]: x_axis[:len(spectrum)], + f"{camera}:SPECTRUM_Y_SUM": spect_sum, + f"{camera}:FIT-COM": np.float64(center), + f"{camera}:FIT-FWHM": np.float64(2.355 * sigma), + f"{camera}:FIT-RMS": np.float64(sigma), + f"{camera}:FIT-RES": np.float64(2.355 * sigma / center * 1000), + f"{camera}:FIT-SPECTRUM_Y": fit_spectrum, + f"{camera}:SPECT-COM": spect_com, + f"{camera}:SPECT-RMS": spect_std, + f"{camera}:SPECT-SKEW": spect_skew, + f"{camera}:SPECT-IQR": spect_iqr, + f"{camera}:SPECT-RES": np.float64(spect_iqr / spect_com * 1000) + } + # Update JSON PV only on ROI change + if roi_changed: + params_json = json.dumps({"roi": global_roi}) + result[f"{camera}:processing_parameters"] = params_json + + # Running averages (thread-safe, incremental sum) + ravg_results = {} + exclude = {parameters["e_int_name"], parameters["e_axis_name"], f"{camera}:processing_parameters"} + with ravg_lock: + for pv in base_pv_names: + if pv not in exclude: + buf = ravg_buffers.setdefault(pv, deque(maxlen=global_ravg_length)) + sum_val = ravg_sum_map.get(pv, 0.0) + if len(buf) == buf.maxlen: + old = buf.popleft() + sum_val -= old + buf.append(result[pv]) + sum_val += result[pv] + ravg_sum_map[pv] = sum_val + ravg_results[pv + "-RAVG"] = sum_val / len(buf) + + # N-shot average (incremental, no full-stack) + global avg_sum + if avg_sum is None: + avg_sum = np.zeros_like(spectrum) + if len(avg_buffer) == global_avg_n: + old = avg_buffer.popleft() + avg_sum -= old + avg_buffer.append(spectrum) + avg_sum += spectrum + avg_spectrum = avg_sum / len(avg_buffer) + # Fit and stats on avg_spectrum + sm_avg = scipy.signal.savgol_filter(avg_spectrum, 51, 3) + min_a, max_a = sm_avg.min(), sm_avg.max() + amp_a = max_a - min_a + skip_a = amp_a <= nrows * 1.5 + offs_a, amp_fit_a, center_a, sigma_a = functions.gauss_fit_psss( + sm_avg[::2], x_axis[:len(sm_avg)][::2], offset=min_a, + amplitude=amp_a, skip=skip_a, maxfev=10 + ) + fit_avg_spectrum = offs_a + amp_fit_a * np.exp(-((x_axis[:len(sm_avg)] - center_a)**2) / (2 * sigma_a**2)) + sm_norm_a = sm_avg / np.sum(sm_avg) + spect_com_a = np.dot(x_axis[:len(sm_norm_a)], sm_norm_a) + spect_std_a = np.sqrt(np.dot((x_axis[:len(sm_norm_a)] - spect_com_a)**2, sm_norm_a)) + spect_skew_a = np.dot((x_axis[:len(sm_norm_a)] - spect_com_a)**3, sm_norm_a) / (spect_std_a**3) + cum_a = np.cumsum(sm_norm_a) + e25_a = np.interp(0.25, cum_a, x_axis[:len(cum_a)]) + e75_a = np.interp(0.75, cum_a, x_axis[:len(cum_a)]) + spect_iqr_a = e75_a - e25_a + spect_res_a = spect_iqr_a / spect_com_a * 1000 + + avg_results = { + f"{camera}:AVG-FIT-COM": np.float64(center_a), + f"{camera}:AVG-FIT-FWHM": np.float64(2.355 * sigma_a), + f"{camera}:AVG-FIT-RMS": np.float64(sigma_a), + f"{camera}:AVG-FIT-RES": np.float64(2.355 * sigma_a / center_a * 1000), + f"{camera}:AVG-SPECT-COM": spect_com_a, + f"{camera}:AVG-SPECT-RMS": spect_std_a, + f"{camera}:AVG-SPECT-SKEW": spect_skew_a, + f"{camera}:AVG-SPECT-IQR": spect_iqr_a, + f"{camera}:AVG-SPECT-RES": np.float64(spect_res_a), + f"{camera}:AVG-SPECTRUM_Y": avg_spectrum, + f"{camera}:AVG-FIT-SPECTRUM_Y": fit_avg_spectrum + } + + # Merge and queue for PV update + full = {**result, **ravg_results, **avg_results} + if epics_lock.acquire(False): + try: + if pulse_id > sent_pid: + sent_pid = pulse_id + buffer.append(tuple(full[pv] for pv in all_pv_names)) + finally: + epics_lock.release() + + return full