This commit is contained in:
root
2025-05-13 10:51:40 +02:00
parent cd7eb8401e
commit e680052592
41 changed files with 938 additions and 292 deletions

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
},

View File

@@ -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
}

View File

@@ -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"
]
}

View File

@@ -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
},

View File

@@ -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
},

View File

@@ -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
},

View File

@@ -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",

View File

@@ -41,6 +41,7 @@
"SATBD01-DSCR120",
"SARBD01-DSCR110",
"S10BD01-DSCR030",
"S10DI01-DSCR020",
"SATBD02-DSCR050",
"SARCL01-DSCR170",
"SARCL02-DSCR280"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -12,10 +12,10 @@
"mode": "PUSH",
"allow_type_changes": false,
"roi_signal": [
1000,
1200,
1400,
848,
1448
1100,
1700
],
"roi_background": [
0,

View File

@@ -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"
}

View File

@@ -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
]
}

View File

@@ -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,

View File

@@ -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
]
}

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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
]
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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",

View File

@@ -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"
]
},

View File

@@ -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

View File

@@ -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:

View File

@@ -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 {}

View File

@@ -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