large reorganization, part 1 done

This commit is contained in:
2024-05-27 16:09:46 +02:00
parent ffbfa2ba92
commit 90e12fc814
42 changed files with 1371 additions and 43856 deletions

View File

@ -0,0 +1,39 @@
from slic.core.adjustable import PVAdjustable
from slic.utils import typename
class AlignmentLaser:
"""Class for the alignment laser"""
def __init__(self, ID, name="Cristallina alignment laser"):
self.ID = ID
self.name = name or ID
pvname_setvalue = ID
pvname_readback = ID
self._adj = PVAdjustable(pvname_setvalue, pvname_readback, accuracy=0, internal=True)
def set_in(self):
self._adj.set_target_value(-19).wait()
def set_out(self):
self._adj.set_target_value(-1).wait()
@property
def status(self):
state = self._adj.get_current_value()
if state is None:
return "not connected"
elif state < -18:
return "In"
elif state > -6:
return "Out"
else:
return "in an undefined position"
# TODO: Save the status downstream of the beamline when laser is out before bringing it back in
def get_downstream_status(self):
return
def __repr__(self):
tn = typename(self)
return f'{tn} "{self.name}" is {self.status}'

5
beamline/apertures.py Normal file
View File

@ -0,0 +1,5 @@
from slic.devices.xoptics import slits
slits149 = slits.SlitUnitCenterWidth('SAROP31-OAPU149')
slits107 = slits.SlitUnitCenterWidth('SAROP31-OAPU107')

View File

@ -0,0 +1,73 @@
from time import sleep
from epics import PV
#from slic.devices.xoptics.aramis_attenuator import Attenuator
pulse_picker_opening_pv = "SARES30-LTIM01-EVR0:RearUniv0-Ena-SP"
def attenuator_scan(transmissions,att,daq,sleep_time=8,label='test',n_pulses=100,detectors=None,pvs=None,channels=None):
PV(pulse_picker_opening_pv).put("Disabled")
sleep(1)
for idx,transmission in enumerate(transmissions):
print("Setting transmission = ",transmission)
att.trans1st(transmission)
sleep(sleep_time)
PV(pulse_picker_opening_pv).put("Enabled")
sleep(1)
print("Acquiring scan point ",idx,", Transmission = ",transmission)
if idx == 0:
#print("First point")
daq.acquire(label,is_scan_step=False,n_pulses=n_pulses,detectors=detectors,channels=channels,pvs=pvs)
else:
#print("Not first point")
daq.acquire(label,is_scan_step=True,n_pulses=n_pulses,detectors=detectors,channels=channels,pvs=pvs)
PV(pulse_picker_opening_pv).put("Disabled")
sleep(1)
print("Returning to first transmission = ",transmissions[0])
att.trans1st(transmissions[0])
sleep(sleep_time)
def attenuator_scan_cont(transmissions,att,daq,sleep_time=8,label='test',n_pulses=100,detectors=None,pvs=None,channels=None):
PV(pulse_picker_opening_pv).put("Disabled")
sleep(1)
for idx,transmission in enumerate(transmissions):
print("Setting transmission = ",transmission)
att.trans1st(transmission)
sleep(sleep_time)
PV(pulse_picker_opening_pv).put("Enabled")
sleep(1)
print("Acquiring scan point ",idx,", Transmission = ",transmission)
if idx < 0:
#print("First point")
daq.acquire(label,is_scan_step=False,n_pulses=n_pulses,detectors=detectors,channels=channels,pvs=pvs)
else:
#print("Not first point")
daq.acquire(label,is_scan_step=True,n_pulses=n_pulses,detectors=detectors,channels=channels,pvs=pvs)
PV(pulse_picker_opening_pv).put("Disabled")
sleep(1)
print("Returning to first transmission = ",transmissions[0])
att.trans1st(transmissions[0])
sleep(sleep_time)

58
beamline/components.py Normal file
View File

@ -0,0 +1,58 @@
from slic.devices.xoptics.aramis_attenuator import Attenuator
from slic.devices.xoptics.pulsepicker import PulsePicker
from slic.devices.xdiagnostics.intensitymonitor import IntensityMonitorPBPS
from .alignment_laser import AlignmentLaser
from slic.devices.xoptics.kb import KBHor, KBVer
from .pp_shutter import PP_Shutter
upstream_attenuator = Attenuator("SARFE10-OATT053", description="Aramis attenuator OATT053")
attenuator = Attenuator("SAROP31-OATA150", description="Cristallina attenuator OATA150")
# Shutter
shutter = PP_Shutter(
"SARES30-LTIM01-EVR0:RearUniv0-Ena-SP", name="Cristallina pulse picker shutter"
) # Shutter button when using the pulse picker
pulsepicker = PulsePicker(
"SAROP31-OPPI151",
"SARES30-LTIM01-EVR0:Pul3",
name="Cristallina X-ray pulse picker OPPI151",
)
front_end_attenuator = Attenuator(
"SARFE10-OATT053", description="Front end attenuator OATT053"
)
# Alignment laser
alignment_laser = AlignmentLaser(
"SAROP31-OLAS147:MOTOR_1", name="Cristallina alignment laser OLAS147"
)
pbps113 = IntensityMonitorPBPS(
"SAROP31-PBPS113",
# vme_crate="SAROP31-CVME-PBPS1", # please check this!
# link=9,
description="Intensity/position monitor in the optics hutch",
)
pbps149 = IntensityMonitorPBPS(
"SAROP31-PBPS149",
# vme_crate="SAROP31-CVME-PBPS2", # please check this!
# link=9,
description="Intensity/position monitor in the experimental hutch",
)
kbHor = KBHor("SAROP31-OKBH154", description="Cristallina horizontal KB mirror")
kbVer = KBVer("SAROP31-OKBV153", description="Cristallina vertical KB mirror")

319
beamline/kb_focusing.py Normal file
View File

@ -0,0 +1,319 @@
from cam_server import PipelineClient
from cam_server.utils import get_host_port_from_stream_address
from bsread import source, SUB
from epics import PV
import numpy as np
import time
import datetime
from pathlib import Path
import json
from loguru import logger
wait_time_benders = 1.0 # can probably be reduced as we wait for the move to finish
wait_time_aperture = 0.5 # can probably be reduced as we wait for the move to finish
def get_position_from_pipeline(pip_instance_id, data_field_name, n_pulses=1):
pipeline_client = PipelineClient()
stream_address = pipeline_client.get_instance_stream(pip_instance_id)
stream_host, stream_port = get_host_port_from_stream_address(stream_address)
with source(host=stream_host, port=stream_port, mode=SUB) as input_stream:
sum_pos = 0
for i in np.arange(n_pulses):
input_stream.connect()
message = input_stream.receive()
pos = message.data.data[data_field_name].value
sum_pos = sum_pos + pos
mean_pos = sum_pos / n_pulses
return mean_pos
def evaluate_bender_scan():
""" Evaluation of data is in /sf/cristallina/applications/optic_tools/KBs
"""
pass
def kbV_focusing_acquire(
bender_us=[1.0, 1.2, 1.49, 1.54, 1.59],
bender_ds=[1.1, 1.3, 1.5, 1.6, 1.79, 1.84],
bender_us_start=1.1,
bender_ds_start=1.33,
aperture=[-0.3, 0, 0.3],
aperture_width=0.15,
aperture_height=1.2,
n_pulses=1,
):
""" Vertical KB mirror focusing acquisition with default parameters.
"""
return kb_focusing_acquire(
direction="vertical",
bender_us=bender_us,
bender_ds=bender_ds,
bender_us_start=bender_us_start,
bender_ds_start=bender_ds_start,
aperture=aperture,
aperture_size=aperture_width,
aperture_size_pendicular=aperture_height,
n_pulses=n_pulses,
)
def kbH_focusing_acquire(
bender_us=[1.55, 1.6, 1.7],
bender_ds=[1.7, 1.8, 1.9],
bender_us_start=1.2, # should be 0.3 below the maximum focus
bender_ds_start=1.5, # should be 0.3 below the maximum focus
aperture=[0.18, 0.48, 0.78],
aperture_height=0.15,
aperture_width=1.8,
n_pulses=1,
):
""" Horizontal KB mirror focusing acquisition with default parameters.
"""
return kb_focusing_acquire(
direction="horizontal",
bender_us=bender_us,
bender_ds=bender_ds,
bender_us_start=bender_us_start,
bender_ds_start=bender_ds_start,
aperture=aperture,
aperture_size=aperture_height,
aperture_size_pendicular=aperture_width,
n_pulses=n_pulses,
)
def kb_focusing_acquire(
direction="vertical",
bender_us=[1.49, 1.54, 1.59],
bender_ds=[1.79, 1.84, 1.89],
bender_us_start=1.29,
bender_ds_start=1.59,
aperture=[-0.3, 0, 0.3],
aperture_size=0.15,
aperture_size_pendicular=1.2,
n_pulses=1,
):
""" KB mirror focusing acquisition routine for Cristallina.
TODO: - split this up into separate routines
- Make inner loop a generator, yielding: bender_us_rb, bender_ds_rb, beam_positions
Input into live analysis.
"""
# Benders
if bender_us_start >= np.min(bender_us) - 0.3:
bender_us_start = max(0, np.min(bender_us) - 0.3)
if bender_ds_start >= np.min(bender_ds) - 0.3:
bender_ds_start = max(0, np.min(bender_ds) - 0.3)
bender_us = np.sort(bender_us)
bender_ds = np.sort(bender_ds)
KBV_NAME = "SAROP31-OKBV153"
KBH_NAME = "SAROP31-OKBH154"
if direction == "vertical":
kb_name = KBV_NAME
elif direction == "horizontal":
kb_name = KBH_NAME
BU = PV(kb_name + ":BU.VAL")
BD = PV(kb_name + ":BD.VAL")
# TODO: is the separation necessary?
BU_RB = PV(kb_name + ":BU.VAL")
BD_RB = PV(kb_name + ":BD.VAL")
# Aperture
aperture = np.sort(aperture)
APU_NAME = "SAROP31-OAPU149"
if direction == "vertical":
APU_CENTER = PV(APU_NAME + ":MOTOR_Y.VAL")
APU_CENTER_RB = PV(APU_NAME + ":MOTOR_Y.RBV")
APU_SIZE = PV(APU_NAME + ":MOTOR_H.VAL")
APU_SIZE_RB = PV(APU_NAME + ":MOTOR_H.RBV")
APU_SIZE_PERPENDICULAR = PV(APU_NAME + ":MOTOR_W.VAL")
APU_SIZE_PERPENDICULAR_RB = PV(APU_NAME + ":MOTOR_W.RBV")
APU_CENTER_PERPENDICULAR = PV(APU_NAME + ":MOTOR_X.VAL")
APU_CENTER_PERPENDICULAR_RB = PV(APU_NAME + ":MOTOR_X.RBV")
# Camera field name
data_field_name = "y_fit_mean"
elif direction == "horizontal":
APU_CENTER = PV(APU_NAME + ":MOTOR_X.VAL")
APU_SIZE = PV(APU_NAME + ":MOTOR_W.VAL")
APU_CENTER_RB = PV(APU_NAME + ":MOTOR_X.RBV")
APU_SIZE_RB = PV(APU_NAME + ":MOTOR_W.RBV")
APU_SIZE_PERPENDICULAR = PV(APU_NAME + ":MOTOR_H.VAL")
APU_SIZE_PERPENDICULAR_RB = PV(APU_NAME + ":MOTOR_H.RBV")
APU_CENTER_PERPENDICULAR = PV(APU_NAME + ":MOTOR_Y.VAL")
APU_CENTER_PERPENDICULAR_RB = PV(APU_NAME + ":MOTOR_Y.RBV")
# Camera field name
data_field_name = "x_fit_mean"
# Camera
CAMERA_NAME = "SARES30-CAMS156-XE"
pip_instance_id = CAMERA_NAME + "_sp"
### Acquisition start
apu_center_ref = APU_CENTER_RB.get()
apu_size_ref = APU_SIZE_RB.get()
apu_size_perp_ref = APU_SIZE_PERPENDICULAR.get()
apu_center_perp_ref = APU_CENTER_PERPENDICULAR.get()
logger.info("BU/BD sent to start")
BU.put(bender_us_start, wait=False)
BD.put(bender_ds_start, wait=False)
APU_SIZE.put(aperture_size, wait=False)
APU_SIZE_PERPENDICULAR.put(aperture_size_pendicular, wait=False)
APU_CENTER.put(aperture[0], wait=False)
BU.put(bender_us_start, wait=True)
BD.put(bender_ds_start, wait=True)
time.sleep(wait_time_benders)
logger.info(f"BU to start: {bender_us_start:6.3f}")
logger.info(f"BD to start: {bender_ds_start:6.3f}")
bender_us_rb = []
bender_ds_rb = []
beam_positions = []
bender_scan_data = {}
datestr, timestr = generate_date_and_time_str()
for bu in bender_us:
logger.info("")
BU.put(bu, wait=False)
for bd in bender_ds:
BU.put(bu, wait=False)
BD.put(bd, wait=False)
BU.put(bu, wait=True)
BD.put(bd, wait=True)
time.sleep(wait_time_benders)
logger.info(f" BU / BD positions = {bu:6.3f} / {bd:6.3f}")
bender_us_rb.append(BU_RB.get())
bender_ds_rb.append(BD_RB.get())
beam_pos = []
for apu in aperture:
APU_CENTER.put(apu, wait=True)
time.sleep(wait_time_aperture)
beam_pos_apu = get_position_from_pipeline(pip_instance_id, data_field_name, n_pulses=n_pulses)
logger.info(f" Aperture position = {apu:6.3f}; Beam position = {beam_pos_apu:6.3f}")
beam_pos.append(beam_pos_apu)
time.sleep(wait_time_aperture)
APU_CENTER.put(aperture[0], wait=False)
beam_positions.append(beam_pos)
BD.put(bender_ds_start, wait=True)
logger.info("")
logger.info(f"BD to start: {bender_ds_start:6.3f}")
time.sleep(wait_time_benders)
# save intermediate data
bender_scan_data["bender_us"] = bender_us_rb
bender_scan_data["bender_ds"] = bender_ds_rb
bender_scan_data["beam_positions"] = beam_positions
fpath = save_focusing_data(bender_scan_data, direction=direction, timestr=timestr, datestr=datestr)
out_fpath = convert_focusing_to_bender_data(fpath)
logger.info(f"BU to start: {bender_us_start:6.3f}")
APU_SIZE.put(apu_size_ref, wait=False)
APU_CENTER.put(apu_center_ref, wait=False)
APU_SIZE_PERPENDICULAR.put(apu_size_perp_ref, wait=False)
APU_CENTER_PERPENDICULAR.put(apu_center_perp_ref, wait=False)
BU.put(bender_us_start, wait=False)
BD.put(bender_ds_start, wait=False)
logger.info(f"Data saved to: {out_fpath}")
logger.info("Done")
return bender_scan_data
def generate_date_and_time_str():
t = datetime.datetime.now()
datestr = t.date().isoformat()
timestr = t.isoformat(timespec='minutes')
return datestr, timestr
def save_focusing_data(bender_scan_data, direction="unknown", timestr=None, datestr=None, beamline_directory="/sf/cristallina/applications/beamline/snapshots/KBs/"):
""" Saves bender focusing data to json for analysis in beamline directory.
"""
bender_scan_data["comment"] = "Cristallina bender focusing data"
if timestr is None:
datestr, timestr = generate_date_and_time_str()
directory = Path(beamline_directory) / datestr
directory.mkdir(parents=True, exist_ok=True)
fpath = directory / f"C_{timestr}_{direction}.json"
with open(fpath, "w") as f:
json.dump(bender_scan_data, f)
return fpath
def convert_focusing_to_bender_data(focusing_json_file):
""" Converts focusing data to text array for further processing.
"""
fpath = Path(focusing_json_file)
with open(fpath, "r") as f:
focusing_data = json.loads(f.read())
nrows = len(focusing_data['bender_us'])
arr = np.empty((nrows, 5))
arr[:, 0] = focusing_data['bender_us']
arr[:, 1] = focusing_data['bender_ds']
arr[:, 2:] = focusing_data['beam_positions']
Diff1 = arr[:,2] - arr[:,3]
Diff2 = arr[:,4] - arr[:,3]
# extend array with difference columns
arr = np.c_[arr, Diff1]
arr = np.c_[arr, Diff2]
out_fpath = fpath.with_suffix(".dat")
np.savetxt(out_fpath, arr)
return out_fpath

32
beamline/pp_shutter.py Normal file
View File

@ -0,0 +1,32 @@
from slic.core.adjustable import PVAdjustable
from slic.utils import typename
class PP_Shutter:
"""Class for when pulse picker is used as a shutter.
Opening and closing is done with turning on and off the event receiver universal output ON and OFF.
"""
def __init__(self, ID, name="X-ray Pulse Picker Shutter"):
self.ID = ID
self.name = name or ID
pvname_setvalue = ID
pvname_readback = ID
self._adj = PVAdjustable(pvname_setvalue, pvname_readback, accuracy=0, internal=True)
def close(self):
self._adj.set_target_value(0).wait()
def open(self):
self._adj.set_target_value(1).wait()
@property
def status(self):
state = self._adj.get_current_value()
if state is None:
return "not connected"
return "open" if state else "closed"
def __repr__(self):
tn = typename(self)
return f'{tn} "{self.name}" is {self.status}'

36
beamline/pulse_picker.py Normal file
View File

@ -0,0 +1,36 @@
from epics import PV
# TODO: all these values have not been checked since the reorganization of the EVR outputs!
def pp_normal(opening = True):
""" Set pulse picker to fixed open or closed mode.
"""
PV("SARES30-LTIM01-EVR0:RearUniv0-Ena-SP").put("Disabled")
PV("SARES30-LTIM01-EVR0:RearUniv0_SOURCE").put("HIGH")
PV("SARES30-LTIM01-EVR0:RearUniv0_SOURCE2").put("HIGH")
if opening:
PV("SARES30-LTIM01-EVR0:RearUniv0-Ena-SP").put("Enabled")
else:
PV("SARES30-LTIM01-EVR0:RearUniv0-Ena-SP").put("Disabled")
def pp_cta():
""" Set pulse picker to use the CTA as control input.
(check connections and parameters!)
"""
PV("SARES30-LTIM01-EVR0:RearUniv0-Ena-SP").put("Disabled")
PV("SARES30-LTIM01-EVR0:RearUniv0_SOURCE").put("PULSER")
PV("SARES30-LTIM01-EVR0:RearUniv0_SOURCE2").put("PULSER")
PV("SARES30-LTIM01-EVR0:RearUniv0_SNUMPD").put("3")
PV("SARES30-LTIM01-EVR0:RearUniv0_SNUMPD2").put("3")
PV("SARES30-LTIM01-EVR0:RearUniv0-Ena-SP").put("Enabled")