added most up-to-date versions of the SwissMX tools/utils

This commit is contained in:
Appleby Martin Vears
2025-06-13 13:44:40 +02:00
parent a67321d959
commit 5a424db2ca
13 changed files with 1650 additions and 0 deletions

View File

@@ -0,0 +1,322 @@
import sys,os,socket
import argparse
sys.path.insert(0, os.path.expanduser('/sf/cristallina/applications/slic/slic-package'))
from slic.devices.timing.events import CTASequencer
class IntRange:
def __init__(self, imin=None, imax=None):
self.imin = imin
self.imax = imax
def __call__(self, arg):
try:
value = int(arg)
except ValueError:
raise self.exception()
if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
raise self.exception()
return value
def exception(self):
if self.imin is not None and self.imax is not None:
return argparse.ArgumentTypeError(f"ON OFF ratio must be an integer in the range [{self.imin}, {self.imax}]")
elif self.imin is not None:
return argparse.ArgumentTypeError(f"ON OFF ratio must be an integer >= {self.imin}")
elif self.imax is not None:
return argparse.ArgumentTypeError(f"ON OFF ratio must be an integer <= {self.imax}")
else:
return argparse.ArgumentTypeError("ON OFF ratio must be an integer")
class tuple_splitter:
def __init__(self, x=None, y=None, ssz=False):
self.x=x
self.y=y
self.ssz=ssz
def __call__(self,arg):
try:
self.x = int(arg.split(',')[0])
self.y = int(arg.split(',')[1])
except ValueError:
raise self.exception()
if self.ssz and self.y != self.x-1:
raise self.exception()
return (self.x,self.y)
def exception(self):
if self.ssz and self.y != self.x-1:
return argparse.ArgumentTypeError(f"for current motion scheme x,y should equal x, x-1 not {x}, {y}")
else:
if self.ssz:
return argparse.ArgumentTypeError("ssz x and y must be an integer")
else:
return argparse.ArgumentTypeError("x and y must be an integer")
class MotionSim:
def __init__(self, args=None):
try:
self.cta=CTASequencer(args.cta_sequencer)
self.clear_flag=args.clear
self.check_sequence=args.check_sequence
self.motion_mode=args.motion_mode
self.repetitions=args.grid_size[0]
self.cta_multiplier=args.grid_size[1]
self.number_of_appertures=self.repetitions*self.cta_multiplier
self.on_off_ratio=args.on_off_ratio+1
self.triggered=args.triggered
self.ssz=args.ssz
self.smv=args.smv
self.twait=args.twait
self.tmove=args.tmove
except:
self.clear=False
self.check_sequence=False
self.cta=CTASequencer("SAR-CCTA-ESC")
self.motion_mode=4
self.repetitions=1
self.cta_multiplier=1
self.number_of_appertures=1
self.on_off_ratio=1
self.triggered=False
raise self.exception()
def exception(self):
if args is not None:
print(args)
return argparse.ArgumentTypeError("problem with arg parse inputs")
else:
return argparse.ArgumentTypeError("no args given, using default values")
def standard_motion(self):
#need to implement on_off_ratio
self.cta.seq[214]=[1,]+[0,]*(self.cta_multiplier-1) #start motion
self.cta.seq[200]=[1,]*self.cta_multiplier #uncomment me for normal operation
self.cta.seq[219]=[1,]*self.cta_multiplier #trigger detector
if self.triggered:
self.cta.seq[215]=[1,0]*(self.cta_multiplier//2) # shutter always open
self.cta.seq[216]=[1,0,]*(self.cta_multiplier//2) # Label image light dark 1:1 #change back to 1,0 for normal on off measurements
self.n_pulses_run = len(self.cta.seq[200])*self.repetitions
self.cta.cfg.repetitions = self.repetitions
self.upload()
return
def hit_and_return(self):
if self.smv:
transition_pulses=self.smv
else:
transition_pulses=self.ssz[0]-1
apperture_group_size = self.ssz[0] * self.ssz[1]
number_of_apperture_groups = self.number_of_appertures // apperture_group_size
self.repetitions = number_of_apperture_groups # number of repeitions is now number of times we repeat the loop
xray_sequence = [0,] * apperture_group_size + [1,] * apperture_group_size + [0,] * (transition_pulses - 1)
self.cta.seq[200] = xray_sequence
self.cta.seq[214] = [1,] + [0,] * (apperture_group_size-1) + [0,] * apperture_group_size + [0,] * (transition_pulses - 1) #start motion
if self.triggered:
if self.on_off_ratio > apperture_group_size:
self.on_off_ratio = apperture_group_size
on_off_sequence = [1,] * (self.on_off_ratio - 1) + [0,]
self.cta.seq[215] = on_off_sequence * (apperture_group_size//self.on_off_ratio) + [0,] * apperture_group_size + [0,] * (transition_pulses -1) # Trigger 1:1
repeat_sequence_laser_closed = [1, ] + [0,] * (apperture_group_size - 1)
repeat_sequence_laser_open = [1,] + [0,] * (apperture_group_size - 1)
tranisition_sequence_laser_shut = [0,] * (transition_pulses - 1) #close shutter after final point before starting move...?
self.cta.seq[213] = repeat_sequence_laser_open + repeat_sequence_laser_closed + tranisition_sequence_laser_shut # Trigger 1:1
self.cta.seq[216] = [0,] * apperture_group_size + on_off_sequence * (apperture_group_size//self.on_off_ratio) + [0,] * (transition_pulses - 1)
else:
self.cta.seq[215] = [0] * apperture_group_size * 2 + [0,] * (transition_pulses - 1) # trigger (laser_shutter or droplet ejector)
self.cta.seq[216] = [0] * apperture_group_size * 2 + [0,] * (transition_pulses -1) # image label (on or off)1
self.cta.seq[219] = xray_sequence # detector trigger
self.cta.cfg.repetitions = self.repetitions
self.upload()
return
def stop_and_go(self):
if self.tmove==0:
print("did you mean tmove=0 and not 10? setting wait_pulses to 0!")
wait_pulses=0
else:
wait_pulses=self.twait//self.tmove
xray_sequence = [0,] * wait_pulses + [1,]
self.cta.seq[200] = xray_sequence * self.cta_multiplier # x-ray_shutter
self.cta.seq[214] = [1,] + [0,] * (len(xray_sequence * self.cta_multiplier) -1) #start motion
if self.triggered:
trigger_on = [1,] + [0,] * wait_pulses
trigger_off = [0,] + [0,] * wait_pulses
trigger_sequence = trigger_off + trigger_on * (self.on_off_ratio-1)
image_on = [0,] * wait_pulses + [1,]
image_label_sequence = [0,] * wait_pulses + [0,] + image_on * (self.on_off_ratio-1)
self.cta.seq[215] = trigger_sequence * (self.cta_multiplier//self.on_off_ratio) # trigger (laser_shutter or droplet ejector)
self.cta.seq[216] = image_label_sequence * (self.cta_multiplier//self.on_off_ratio) # image label (on or off)
else:
no_trigger_sequence = [0,] + [0,] * wait_pulses
self.cta.seq[215] = no_trigger_sequence * self.cta_multiplier # x-ray_shutter # trigger (laser_shutter or droplet ejector)
self.cta.seq[216] = no_trigger_sequence * self.cta_multiplier # x-ray_shutter # image label (on or off)1
self.cta.seq[219] = xray_sequence * self.cta_multiplier # detector trigger
self.cta.cfg.repetitions = self.repetitions
self.upload()
return
def upload(self):
self.cta.seq.upload()
return
def clear(self):
self.cta.seq[200]=[1,]
self.upload()
return
def single_event(self, event:int):
#self.cta.seq[200]=[0,]*99+[1,]
self.cta.seq[event]=[0,]*99+[1,]
self.upload()
return
def check(self):
self.cta.seq.download()
print(self.cta.seq)
try:
print(f"length of sequence: {len(self.cta.seq[200])}")
except Exception as e:
print(f"Exception {e}")
return
def main(args):
ctaSeq = MotionSim(args)
if args.clear:
print("Clearing the CTA (has to have an event so setss [200] to [1]")
ctaSeq.clear()
return
elif args.check_sequence:
print("Checking the current CTA sequence")
ctaSeq.check()
return
elif args.droplet:
print("Clearing the CTA and priming a single event of [200] and [215] for aligning the droplet ejector")
event=215
ctaSeq.single_event(event)
elif args.shutter:
print("Clearing the CTA and priming a single event of [200] and [213] for aligning the droplet ejector")
event=213
ctaSeq.single_event(event)
elif args.motion_mode == 1 or args.motion_mode == 2 or args.motion_mode == 3:
print("probably doesnt use or doesn't need the cta. But would use a similar scheme to motion_mode 4")
ctaSeq.standard_motion()
event=200
elif args.motion_mode == 4:
ctaSeq.standard_motion()
event=200
elif args.motion_mode == 5:
ctaSeq.stop_and_go()
event=200
elif args.motion_mode == 6:
ctaSeq.hit_and_return()
event=200
else:
print('nothing happened')
return
n_pulses_run = len(ctaSeq.cta.seq[event])*ctaSeq.repetitions
bsdata_scalar = n_pulses_run/ctaSeq.number_of_appertures
print(f"uploading sequence to cta of {n_pulses_run} pulses. Sequence length: {len(ctaSeq.cta.seq[200])} for one event. Repetitions: {ctaSeq.repetitions}.")
print(f"BS data would be scaled by a factor of {bsdata_scalar}.")
ctaSeq.upload()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-c",
"--clear",
help="replace current cta sequence",
action='store_true',
)
parser.add_argument(
"-m",
"--motion_mode",
help="Mimic cta code generation for specific motion code, limit:1-6. Mode:1 point list, mode:2 unused, mode 3: inverse list, mode 4: DEFAULT (short code), motion 5: stop and go, motion 6: hit and return",
type=IntRange(1,6),
default=4
)
parser.add_argument(
"-g",
"--grid_size",
help="size of grid, default 162,162",
type=tuple_splitter(ssz=False),
default='162,162'
)
parser.add_argument(
"-o",
"--on_off_ratio",
help="the number of on shots (i.e. laser triggers, drolet ejector) per single dark shot. i.e. 1 would give you a 1 on: 1 off ratio. limit:1-100, Note for hit and return the ",
type=IntRange(1,100),
default=1
)
parser.add_argument(
"-w",
"--twait",
help="input the wait time in multiples of 10 i.e. 10 ms, 20 ms.",
type=int,
default=10
)
parser.add_argument(
"-s",
"--ssz",
help="needed for motion mode 6, size fo the repeating grid i.e. 4x3, 8x7. Currently limited in real motion to x,x-1 due to smv requirement",
type=tuple_splitter(ssz=True),
default=None
)
parser.add_argument(
"--check_sequence",
help="print current cta sequence",
action='store_true',
)
parser.add_argument(
"-t",
"--triggered",
help="print current cta sequence",
action='store_true',
)
parser.add_argument(
"--tmove",
help="time between moves for motion mode 5 = stop and go, should be 10",
type=int,
default=10
)
parser.add_argument(
"--smv",
help="# of pulses to move between repeats in x and y i.e 3,3 would be 3 pulses to change in x and 3 pulses to change in y. Default value x-1. Currently must be same in x and y due to cta limitations.",
type=int,
default=None
)
parser.add_argument(
"--cta_sequencer",
help="name of sequencer PV, default = SAR-CCTA-ESC",
type=str,
default="SAR-CCTA-ESC"
)
parser.add_argument(
"--droplet",
help="for aligning droplet ejector, event 215 at 100 hz",
action='store_true',
)
parser.add_argument(
"--shutter",
help="for aligning laser during hit and return, event 213 at 100 hz",
action='store_true',
)
args = parser.parse_args()
if args.motion_mode == 5 and args.twait is None:
parser.error("--motion_mode = 5 requires twait to define wait time at each point.")
if args.motion_mode == 6 and args.ssz is None:
parser.error("--motion_mode = 6 requires ssz to define repeating grid.")
main(args)

276
cta/list_sort_clara.py Normal file
View File

@@ -0,0 +1,276 @@
import h5py
import sys, os
import argparse
import numpy as np
from pathlib import Path
sys.path.insert(0, os.path.expanduser('/sf/cristallina/applications/mx/slic'))
def sort_event_laser(events, ind_JF, ind_BS):
#sort by event 63 logic
index_dark = []
index_light = []
blanks = []
for idx_JF, idx_BS in zip(ind_JF, ind_BS):
events = evt_set[idx_BS]
if events[63] and events[200]:
index_dark.append(idx_JF)
elif events[200]:
index_light.append(idx_JF)
else:
print(f"unknown event: {events}")
blanks.append(idx_JF)
def sort_event_cta(events, event ind_JF, ind_BS):
#sort by cta event logic i.e. 215/216
index_dark = []
index_light = []
blanks = []
for idx_JF, idx_BS in zip(ind_JF, ind_BS):
events = evt_set[idx_BS]
if events[trigger_event] and events[200]:
index_light.append(idx_JF)
elif events[200]:
index_dark.append(idx_JF)
else:
print(f"unknown event: {events}")
blanks.append(idx_JF)
def sort_jfjoch_header(jf_data, pulseids_JF):
index_dark = []
index_light = []
blanks = []
evt_set_jfj = jf_data[f"/entry/xfel/eventCode"][:]
n_pulse_id = len(pulseids_JF)
for idx_JF in range(n_pulse_id):
events = evt_set_jfj[idx_JF]
if events == 15:
index_dark.append(idx_JF)
elif events == 13:
index_light.append(idx_JF)
else:
print(f"unknown event: {events}")
blanks.append(idx_JF)
return index_dark, index_light, blank
def sort_bsdata(data_directory, event, jf_data, pulseids_JF):
for acquisition in range(1,27):
bsdata_path = os.path.join(data_directory,'data','acq{0:0=4d}.BSDATA.h5'.format(acquisition))
try:
#bsdata = SFDataFiles(bsdata_path)
bsdata = h5py.File(bsdata_path, "r") #r"/sf/cristallina/data/p21630/raw/run0065-lov_movestop_normal_1/data/acq0001.BSDATA.h5"
except Exception as e:
print(f"didn't open bsdata due to error {e}")
return
pulseids_BS = bsdata[f"/SAR-CVME-TIFALL6:EvtSet/pulse_id"][:]
evt_set=bsdata[f"/SAR-CVME-TIFALL6:EvtSet/data"][:]
_inters, ind_JF, ind_BS = np.intersect1d(pulseids_JF, pulseids_BS, return_indices=True)
if trigger_event == 63:
index_dark, index_light, blank = sort_event_laser(events, ind_JF, ind_BS)
else:
index_dark, index_light, blank = sort_event_cta(events, event ind_JF, ind_BS)
bsdata.close()
return index_dark, index_light, blank
def sort_acquisition(data_directory, acquisition, output_name, event, jfjoch_header):
detector='JF17T16V01'
jf_path= os.path.join(data_directory,'data','acq{0:0=4d}.{1}.h5'.format(acquisition,detector))
print(jf_path)
try:
jf_data = h5py.File(jf_path, "r")
except Exception as e:
print(f"didn't open JF17T16V01.h5 due to error {e}")
return
pulseids_JF = jf_data[f"/entry/xfel/pulseID"][:]
if jfjoch_header:
index_dark, index_light, blank = sort_jfjoch_header(jf_data, pulseids_JF)
else:
index_dark, index_light, blank = sort_bsdata(data_directory, event, jf_data, pulseids_JF)
jf_data.close()
acq_dark = []
acq_light = []
acq_blank = []
delim = "//"
if index_dark:
for frame_number in index_dark:
acq_dark.append(f"{jf_path} {delim}{frame_number}")
if index_light:
for frame_number in index_light:
acq_light.append(f"{jf_path} {delim}{frame_number}")
if blanks:
for frame_number in blanks:
acq_blank.append(f"{jf_path} {delim}{frame_number}")
return acq_light, acq_dark, acq_blank
def generate_list_file_all(output_name, index_dark=None, index_light=None, blanks=None):
delim = "//"
if index_dark:
file_dark = output_name + ".dark.lst"
print(f"List of dark frames : {file_dark} , {len(index_dark)} frames")
dir_dark=f"{output_name}_off"
file_off = f"{dir_dark}/{acquisition}.off.lst"
Path(dir_dark).mkdir(parents=True, exist_ok=True)
with open(file_off, "w") as f_list:
for frame in acq_dark:
print(f"{frame}", file = f_list)
if index_light:
file_light = output_name + ".light.lst"
print(f"List of light frames : {file_light} , {len(index_light)} frames")
dir_light=f"{output_name}_on"
file_on = f"{dir_light}/{acquisition}.on.lst"
Path(dir_light).mkdir(parents=True, exist_ok=True)
with open(file_on, "w") as f_list:
for frame in acq_light:
print(f"{frame}", file = f_list)
if blanks:
file_blank = output_name + ".blanks.lst"
print(f"List of blank frames : {file_blank} , {len(blanks)} frames")
dir_blank=f"{output_name}_blank"
file_blank = f"{dir_blank}/{acquisition}.blank.lst"
Path(dir_blank).mkdir(parents=True, exist_ok=True)
with open(file_blank, "w") as f_list:
for frame in acq_blank:
print(f"{frame}", file = f_list)
def generate_list_files(output_name, index_dark=None, index_light=None, blanks=None, acquisition=None):
delim = "//"
acq='acq{0:0=4d}'.format(acquisition)
if index_dark:
dir_dark=f"{output_name}_off"
file_dark = f"{dir_dark}/{acq}.off.lst"
Path(dir_dark).mkdir(parents=True, exist_ok=True)
print(f"List of dark frames : {file_dark} , {len(index_dark)} frames")
with open(file_dark, "w") as f_list:
for frame in index_dark:
print(f"{frame}", file = f_list)#{data_file} {delim}{frame_number}
if index_light:
dir_light=f"{output_name}_on"
Path(dir_light).mkdir(parents=True, exist_ok=True)
file_light = f"{dir_light}/{acq}.on.lst"
print(f"List of light frames : {file_light} , {len(index_light)} frames")
with open(file_light, "w") as f_list:
for frame in index_light:
print(f"{frame}", file = f_list)#{data_file} {delim}{frame_number}
if blanks:
dir_blank=f"{output_name}_blank"
Path(dir_blank).mkdir(parents=True, exist_ok=True)
file_blank = f"{dir_blank}/{acq}.blanks.lst"
print(f"List of blank frames : {file_blank} , {len(blanks)} frames")
with open(file_blank, "w") as f_list:
for frame in blanks:
print(f"{frame}", file = f_list)#{data_file} {delim}{frame_number}
def main(args):
data_directory = args.input_data_directory
event = args.event
jfjoch_header=args.jfjoch_header
try:
output_name = args.output_name
except:
output_name=data_directory.split('/')[-1]
number_of_acqusitions = os.listdir(os.path.join(data_directory,'meta'))
index_light_all=[]
index_dark_all=[]
blanks_all = []
for i in range(1,len(number_of_acqusitions)):
acq_light,acq_dark,acq_blank= sort_acquisition(data_directory, i, output_name)
generate_list_files(output_name, acq_dark, acq_light, acq_blank, acquisition=i)
index_light_all+=(acq_light)
index_dark_all+=(acq_dark)
blanks_all+=(acq_blank)
generate_list_file_all(output_name, index_dark_all, index_light_all, blanks_all)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--input_data_directory",
help="raw data directory and run i.e. /sf/cristallina/data/p21630/raw/run0065-lov_movestop_normal_1/",
type=os.path.abspath,
required=True
)
parser.add_argument(
"-o",
"--ouput_name",
help="the name of the job to be done. if not included file names will be automatically generated based on input run name",
type=str,
)
parser.add_argument(
"-j",
"--jfjoch_header",
help="flag to sort by jfjoch_header",
action='store_true',
)
parser.add_argument(
"-e",
"--event_label_trigger",
help="number of event trigger i.e. 63, 216.",
type=int,
default=216
)
args = parser.parse_args()
main(args)

View File

@@ -0,0 +1,112 @@
import h5py
import sys, os
import argparse
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def sort_acquisition(data_directory, acquisition):
detector='JF17T16V01j'
bsdata_path = os.path.join(data_directory,'data','acq{0:0=4d}.BSDATA.h5'.format(acquisition))
try:
f = h5py.File(bsdata_path, "r") #r"/sf/cristallina/data/p21630/raw/run0065-lov_movestop_normal_1/data/acq0001.BSDATA.h5"
except Exception as e:
print("didn't open bsdata due to error {e}") #_logger.error(f"Cannot open {data_file} (due to {e})")
return
jf_path=os.path.join(data_directory,'data','acq{0:0=4d}.{1}.h5'.format(acquisition, detector))
print(jf_path)
try:
#r"/sf/cristallina/data/p21630/raw/run0065-lov_movestop_normal_1/data/acq0001.JF17T16V01.h5"
x = h5py.File(jf_path, "r")
except Exception as e:
print("didn't open JF17T16V01.h5 due to error {e}") #_logger.error(f"Cannot open {data_file} (due to {e})")
return
pulseids_JF = x[f"/entry/xfel/pulseID"][:]
pulseids_BS = f[f"/SAR-CVME-TIFALL6:EvtSet/pulse_id"][:]
power_meter = pd.DataFrame(f[f"/SARES33-GPS:PR1_CH0_VAL_GET/data"][:], columns=['data']) #gass monitor
power_meter.index = f[f"/SARES33-GPS:PR1_CH0_VAL_GET/pulse_id"][:]
pulseids_psss = f[f"/SARFE10-PSSS059:SPECTRUM_Y_SUM/pulse_id"][:] #Group PSSS
evt_set=pd.DataFrame(f[f"/SAR-CVME-TIFALL6:EvtSet/data"][:])
evt_set.index = f[f"/SAR-CVME-TIFALL6:EvtSet/pulse_id"][:]
common_pulses = power_meter.index.intersection(evt_set.index)
power_meter= power_meter.loc[common_pulses]
common_pulses = evt_set.index.intersection(power_meter.index)
evt_set = evt_set.loc[common_pulses]
print(power_meter.index.duplicated())
x=power_meter.index.duplicated()
for i in range(len(power_meter.index)):
if x[i] == True:
print(power_meter.index[i])
fig, ax1 = plt.subplots()
color = 'tab:red'
ax1.set_xlabel('pulse_id')
ax1.set_ylabel('exp', color=color)
ax1.scatter(common_pulses, evt_set.loc[common_pulses][evt_set.columns[63]], color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.invert_yaxis()
ax2 = ax1.twinx() # instantiate a second Axes that shares the same x-axis
color = 'tab:blue'
ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
ax2.scatter(common_pulses, power_meter.loc[common_pulses]['data'].values, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()
plt.scatter(power_meter['data'].loc[common_pulses].values, evt_set.loc[common_pulses][evt_set.columns[63]])
#plt.scatter(common_pulses, power_meter['data'].values[1:])
plt.show()
def main(args):
data_directory = args.input_data_directory
try:
output_name = args.output_name
except:
output_name=data_directory.split('/')[-1]
number_of_acqusitions = os.listdir(os.path.join(data_directory,'meta'))
index_light=[]
index_dark=[]
blanks = []
for i in range(len(number_of_acqusitions)):
print(i)
sort_acquisition(data_directory, i)
#index_light+=(acq_light)
#index_dark+=(acq_dark)
#blanks+=(acq_blank)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--input_data_directory",
help="raw data directory and run i.e. /sf/cristallina/data/p21630/raw/run0065-lov_movestop_normal_1/",
type=os.path.abspath,
required=True
)
parser.add_argument(
"-o",
"--ouput_name",
help="the name of the job to be done. if not included file names will be automatically generated based on input run name",
type=str,
)
parser.add_argument(
"-l",
"--log_file_name",
help="the name of the logger.",
type=str,
default='logging_logger_of_logs.log'
)
args = parser.parse_args()
#logfile = args.log_file_name
#logger.add( logfile, format="{message}", level="INFO")
main(args)