Merge pull request 'refactoring' (#73) from feat/digital_twin into main
CI for debye_bec / test (push) Successful in 1m7s
CI for debye_bec / test (push) Successful in 1m7s
Reviewed-on: #73
This commit was merged in pull request #73.
This commit is contained in:
@@ -1,242 +0,0 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
os.environ["USE_XRT"] = "False"
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
def calc_positions(cfg):
|
||||
|
||||
pos = {}
|
||||
|
||||
## FE slits
|
||||
trxr = -np.arctan(cfg['h_acc'])*bl.feSlits.center1[1]
|
||||
trxw = (np.arctan(cfg['h_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
||||
tryb = -np.arctan(cfg['v_acc'])*bl.feSlits.center1[1]
|
||||
tryt = (np.arctan(cfg['v_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
||||
|
||||
# trxw_proj = trxw/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
# tryt_proj = tryt/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
|
||||
# xcen = (trxr + trxw) / 2
|
||||
# ycen = (tryb + tryt) / 2
|
||||
xgap = trxw - trxr
|
||||
ygap = tryt - tryb
|
||||
|
||||
pos['sldi_gapx'] = {'value': xgap}
|
||||
pos['sldi_gapy'] = {'value': ygap}
|
||||
|
||||
## Collimating Mirror
|
||||
obj_dist = bl.cm.center[1] # object distance
|
||||
beam_vs = 2 * obj_dist * np.tan(cfg['v_acc']) # vertical size of beam after CM
|
||||
|
||||
# TRX
|
||||
try:
|
||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
||||
except:
|
||||
raise ValueError(f"Requested stripe {cfg['cm_stripe']} not found in parameters!")
|
||||
cm_trx = -(bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
||||
pos['cm_trx'] = {'value': cm_trx}
|
||||
|
||||
# TRY
|
||||
height = obj_dist * np.tan(cfg['v_acc'])**2 * 1 / np.tan(cfg['cm_pitch'])
|
||||
pos['cm_try'] = {'value': height}
|
||||
|
||||
# Pitch
|
||||
pos['cm_rotx'] = {'value': -cfg["cm_pitch"]*1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
# Bending Radius
|
||||
radius = 2. * obj_dist / np.sin(cfg['cm_pitch']) # Elements of modern X-ray Physics, page 108 ff.
|
||||
pos['cm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
||||
|
||||
## Monochromator
|
||||
# Bragg Angle
|
||||
# if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# # Add 2x CM pitch to the bragg angle
|
||||
# bragg = ((2 * cfg['cm_pitch']) + cfg['mo1_bragg']) / np.pi * 180
|
||||
# elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# # Align xtal surfaces parallel to beam
|
||||
# bragg = (2 * cfg['cm_pitch']) / np.pi * 180
|
||||
# else:
|
||||
# raise Exception('Monochromator mode not supported')
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg = cfg['mo1_bragg']
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg = 0
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
pos['mo1_bragg_angle'] = {'value': bragg/np.pi*180} # Bragg angle in deg
|
||||
|
||||
# TRY, Height
|
||||
l = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg'])
|
||||
yhor = l*np.cos(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))
|
||||
yver = yhor*np.tan(2.*cfg['cm_pitch'])
|
||||
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
beamOffsetCCM = l*np.sin(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))-yver # Resultat ist korrekt!
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
beamOffsetCCM = 0
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
|
||||
def csc(a):
|
||||
return 1/np.sin(a)
|
||||
|
||||
def cot(a):
|
||||
return 1/np.tan(a)
|
||||
|
||||
# calculate height of center of first crystal surface
|
||||
f = bl.mo1.rotOffset # rotation offset, mm
|
||||
# logger.info(f'f = {f}')
|
||||
d = bl.mo1.heightOffset # xtal height offset, mm
|
||||
# logger.info(f'd = {d}')
|
||||
c = d*csc(cfg['mo1_bragg'])-f*cot(cfg['mo1_bragg'])
|
||||
# logger.info(f'c = {c}')
|
||||
|
||||
# Calculate height of center of rotation
|
||||
b = np.sqrt(d**2*csc(cfg['mo1_bragg'])**2-2*d*f*cot(cfg['mo1_bragg'])*csc(cfg['mo1_bragg'])+f**2*cot(cfg['mo1_bragg'])**2+f**2)
|
||||
# logger.info(f'b = {b}')
|
||||
h = np.cos(np.pi/2-np.arctan(f/c)-cfg['mo1_bragg']-2*cfg['cm_pitch'])*b
|
||||
# logger.info(f'h = {h}')
|
||||
h2 = ((bl.mo1.center[1] - bl.cm.center[1])-np.sqrt(b**2-h**2))*np.tan(2*cfg['cm_pitch'])
|
||||
# logger.info(f'mo1 = {bl.mo1.center[1]}')
|
||||
# logger.info(f'cm = {bl.cm.center[1]}')
|
||||
# logger.info(f'pitch = {cfg["cm_pitch"]}')
|
||||
# logger.info(f'h2 = {h2}')
|
||||
#TODO Mono height not exactly the same as in raytracing
|
||||
heightCCM1real = h + h2 # per design, the height should not change if the pitch of the CM is not changed!
|
||||
# heightCCM1real = heightCCM1real - 30 # Zero position of stage is at 1430 mm from ground.
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
pass
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
heightCCM1real = heightCCM1real - 13 # Move down to let beam pass between both crystal without touching copper cooler
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
pos['mo1_try'] = {'value': heightCCM1real}
|
||||
|
||||
# TRX, Crystal selection
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
try:
|
||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
except:
|
||||
raise ValueError(f"Requested xtal {xtal} not found in parameters!")
|
||||
pos['mo1_trx'] = {'value': bl.mo1.xtalOffsetX[index]}
|
||||
else:
|
||||
pos['mo1_trx'] = {'value': 0}
|
||||
|
||||
|
||||
#TODO move to mono, calc for beam Z-movement between crystal surfaces
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
||||
dz = diag * np.cos(2 * (cfg['cm_pitch'] + cfg['mo1_bragg']))
|
||||
|
||||
## Slits 1
|
||||
d = bl.opSlits1.center[1] - bl.cm.center[1] - dz
|
||||
sl1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
||||
pos['sl1_centery'] = {'value': sl1_beam_height}
|
||||
pos['sl1_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 1
|
||||
d = bl.opBM1.center[1] - bl.cm.center[1] - dz
|
||||
# logger.info(f'distance: {d}')
|
||||
# logger.info(f'cm pitch: {cfg["cm_pitch"]}')
|
||||
# logger.info(f'mono offset: {beamOffsetCCM}')
|
||||
bm1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
||||
pos['bm1_try'] = {'value': bm1_beam_height}
|
||||
|
||||
## Focusing Mirror
|
||||
p = bl.fm.center[1]
|
||||
q = cfg['smpl'] - bl.fm.center[1]
|
||||
f = (p*q)/(p+q) # focal length
|
||||
|
||||
# Bender radius
|
||||
if cfg['fm_qy'] is None:
|
||||
radius = 2 * q / np.sin(cfg['fm_rotx']) # ideal bending radius for focused beam
|
||||
else:
|
||||
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_rotx']) # ideal bending radius for unfocused beam
|
||||
pos['fm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
||||
|
||||
# Pitch
|
||||
d = bl.fm.center[1] - bl.cm.center[1] - dz
|
||||
fm_rotx = 2 * cfg['cm_pitch'] - cfg['fm_rotx'] # calculate pitch in absolute values (according to horizontal plane)
|
||||
pos['fm_rotx'] = {'value': -fm_rotx * 1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
# TRY
|
||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
||||
r = bl.fm.r[0]
|
||||
h_cyl = bl.fm.hToroid[0]
|
||||
else: # PT toroid
|
||||
r = bl.fm.r[1]
|
||||
h_cyl = bl.fm.hToroid[1]
|
||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'] * 1e-3)
|
||||
alpha = np.arccos(1 - widthBeam**2 / (2 * r**2))
|
||||
h = r - (r * np.cos(alpha / 2))
|
||||
fm_beam_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM - h_cyl + h / 2) * cfg['fm_gain_height']
|
||||
pos['fm_try'] = {'value': fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
||||
x_cyl = - bl.fm.xToroid[0]
|
||||
else:
|
||||
x_cyl = - bl.fm.xToroid[1]
|
||||
pos['fm_trx'] = {'value': x_cyl}
|
||||
|
||||
elif cfg['fm_stripe'] in ('Rh (flat)', 'Pt (flat)'):
|
||||
|
||||
# TRY
|
||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
||||
fm_beam_height = fm_height
|
||||
pos['fm_try'] = {'value': fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg['fm_stripe'] in 'Rh (flat)':
|
||||
x_flat = - bl.fm.xFlat[0]
|
||||
else:
|
||||
x_flat = - bl.fm.xFlat[1]
|
||||
pos['fm_trx'] = {'value': x_flat}
|
||||
|
||||
else:
|
||||
raise Exception('FM Stripe selection not valid')
|
||||
|
||||
pos['fm_roty'] = {'value': 0}
|
||||
pos['fm_rotz'] = {'value': 0}
|
||||
|
||||
## Slits 2
|
||||
d = bl.opSlits2.center[1] - bl.fm.center[1]
|
||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['sl2_centery'] = {'value': sl2_beam_height}
|
||||
pos['sl2_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 2
|
||||
d = bl.opBM2.center[1] - bl.fm.center[1]
|
||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['bm2_try'] = {'value': bm2_beam_height}
|
||||
|
||||
## Optical Table
|
||||
|
||||
# TRY
|
||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
# logger.info(fm_height)
|
||||
# logger.info(d * np.tan((2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])))
|
||||
pos['ot_try'] = {'value': ot_height}
|
||||
|
||||
# Pitch
|
||||
ot_pitch = - (2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])
|
||||
pos['ot_rotx'] = {'value': ot_pitch * 1e3}
|
||||
|
||||
# TRZ ES1
|
||||
ot_es1_trz = cfg['smpl']
|
||||
pos['ot_es1_trz'] = {'value': ot_es1_trz}
|
||||
|
||||
# ES0 exit window
|
||||
pos['es0wi_try'] = {'value': 5} # At 5mm, the middle of the window is 500 mm from the table (neutral position)
|
||||
|
||||
return pos
|
||||
@@ -1,42 +0,0 @@
|
||||
import numpy as np
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
def calc_sideview(cfg):
|
||||
|
||||
# Calculate height of beam after CM
|
||||
height = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
||||
|
||||
# beam height (Y=height, Z=along beam)
|
||||
beam = {}
|
||||
beam['x'] = []
|
||||
beam['y'] = []
|
||||
beam['x'].append(0) # Source
|
||||
beam['y'].append(bl.sourceHeight)
|
||||
beam['x'].append(bl.cm.center[1]) # CM
|
||||
beam['y'].append(bl.sourceHeight)
|
||||
if cfg['mo1_mode'] in 'Monochromatic':
|
||||
diag = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
||||
dy = diag*np.sin(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
||||
dz = diag*np.cos(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
||||
beam['x'].append(bl.mo1.center[1]-dz/2) # Mono 1.1
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1]))
|
||||
beam['x'].append(bl.mo1.center[1]+dz/2) # Mono 1.2
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])+dy)
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy)
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]))
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
|
||||
dy_fm_ex = beam['y'][-1] - beam['y'][-2]
|
||||
dz_fm_ex = beam['x'][-1] - beam['x'][-2]
|
||||
dz_fm_win = bl.ehWindow.center[1] - beam['x'][-2]
|
||||
h_at_win = beam['y'][-2] + dy_fm_ex / dz_fm_ex * dz_fm_win
|
||||
|
||||
beam['heightWindow'] = h_at_win
|
||||
|
||||
return beam
|
||||
@@ -1,131 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
os.environ["USE_XRT"] = "False"
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
def calc_surfaces(cfg):
|
||||
|
||||
out = {
|
||||
'cm': {'x': [], 'y': []},
|
||||
'mo1_1': {'x': [], 'y': []},
|
||||
'mo1_2': {'x': [], 'y': []},
|
||||
'fm': {'x': [], 'y': []},
|
||||
}
|
||||
|
||||
# Collimating mirror
|
||||
l = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])/np.sin(cfg['cm_pitch'])
|
||||
|
||||
w1 = 2 * (bl.cm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
||||
w2 = 2 * (bl.cm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
||||
|
||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
||||
cen = (bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
||||
|
||||
if cfg['cm_trx'] is not None:
|
||||
cen = cfg['cm_trx']
|
||||
|
||||
out['cm']['x'] = [cen-w1/2, cen-w2/2, cen+w2/2, cen+w1/2]
|
||||
out['cm']['y'] = [-l/2, l/2, l/2, -l/2]
|
||||
|
||||
|
||||
# Monochromator
|
||||
# calculate height of center of first crystal surface
|
||||
c = bl.mo1.heightOffset*1/np.sin(cfg['mo1_bragg'])-bl.mo1.rotOffset*1/np.tan(cfg['mo1_bragg'])
|
||||
e = bl.mo1.xtalGap[0]/np.tan(cfg['mo1_bragg'])-c
|
||||
|
||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
|
||||
xtalPos = bl.mo1.xtalOffsetX[index]
|
||||
xtalLength1 = bl.mo1.xtalLength1[index]
|
||||
xtalLength2 = bl.mo1.xtalLength2[index]
|
||||
|
||||
widthBeam = 2 * bl.mo1.center[1] * np.tan(cfg['h_acc'])
|
||||
|
||||
heightBeam = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
||||
w = heightBeam / np.sin(cfg['mo1_bragg'])
|
||||
|
||||
if cfg['mo1_mode'] in 'Monochromatic':
|
||||
out['mo1_1']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
||||
out['mo1_1']['y'] = [xtalLength1/2-c-w/2, xtalLength1/2-c-w/2, xtalLength1/2-c+w/2, xtalLength1/2-c+w/2]
|
||||
out['mo1_2']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
||||
out['mo1_2']['y'] = [-xtalLength2/2+e-w/2, -xtalLength2/2+e-w/2, -xtalLength2/2+e+w/2, -xtalLength2/2+e+w/2]
|
||||
else: # Pinkbeam
|
||||
out['mo1_1']['x'] = []
|
||||
out['mo1_1']['y'] = []
|
||||
out['mo1_2']['x'] = []
|
||||
out['mo1_2']['y'] = []
|
||||
|
||||
# Focusing mirror
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
surface = bl.fm.surfaceToroid
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
||||
index = surface.index(stripe)
|
||||
off = (bl.fm.limOptXToroid[0][index] + bl.fm.limOptXToroid[1][index]) / 2
|
||||
r = bl.fm.r[index]
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
||||
index = surface.index(stripe)
|
||||
off = (bl.fm.limOptXFlat[0][index] + bl.fm.limOptXFlat[1][index]) / 2
|
||||
r = bl.fm.r[index]
|
||||
if cfg['fm_trx'] is not None:
|
||||
off = cfg['fm_trx']
|
||||
|
||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'])
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
alpha = np.arccos(1-widthBeam**2/(2*r**2))
|
||||
h = r-(r*np.cos(alpha/2))
|
||||
z = h/np.tan(cfg['fm_rotx'])
|
||||
|
||||
x = [off-widthBeam/2, off-widthBeam/2]
|
||||
y = [l/2-z/2, -l/2-z/2]
|
||||
|
||||
# logger.info(f'stripe: {cfg["fm_stripe"]}')
|
||||
# logger.info(f'fm_rotx: {cfg["fm_rotx"]}')
|
||||
# logger.info(f'h: {h}')
|
||||
# logger.info(f'z: {z}')
|
||||
# logger.info(f'r: {r}')
|
||||
|
||||
res = 20
|
||||
xElipse = np.linspace(0, np.pi, res)
|
||||
yElipse = np.linspace(0, np.pi, res)
|
||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam-l/2-z/2 for i in yElipse]
|
||||
|
||||
x.extend(xElipse)
|
||||
y.extend(yElipse)
|
||||
|
||||
x.extend([off+widthBeam/2, off+widthBeam/2])
|
||||
y.extend([-l/2-z/2, l/2-z/2])
|
||||
|
||||
res = 50
|
||||
xElipse = np.linspace(np.pi, 0, res)
|
||||
yElipse = np.linspace(np.pi, 0, res)
|
||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam+l/2-z/2 for i in yElipse]
|
||||
|
||||
x.extend(xElipse)
|
||||
y.extend(yElipse)
|
||||
|
||||
out['fm']['x'] = x
|
||||
out['fm']['y'] = y
|
||||
|
||||
else: # flat surface, no toroid
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
|
||||
w1 = 2 * (bl.fm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
||||
w2 = 2 * (bl.fm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
||||
|
||||
out['fm']['x'] = [off-w1/2, off+w1/2, off+w2/2, off-w2/2]
|
||||
out['fm']['y'] = [-l/2, -l/2, l/2, l/2]
|
||||
|
||||
return out
|
||||
@@ -1,206 +0,0 @@
|
||||
import re
|
||||
import numpy as np
|
||||
from scipy.interpolate import UnivariateSpline
|
||||
from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO
|
||||
from bec_lib import bec_logger
|
||||
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
def sldi_gap_to_acc(sldi_gapx, sldi_gapy):
|
||||
d1 = bl.feSlits.center1[1]
|
||||
d2 = bl.feSlits.center2[1]
|
||||
h_acc = np.tan(sldi_gapx / (d2 + d1))
|
||||
v_acc = np.tan(sldi_gapy / (d2 + d1))
|
||||
|
||||
# h_acc = np.tan(sldi_gapx / (2 * d1))
|
||||
# v_acc = np.tan(sldi_gapy / (2 * d1))
|
||||
return h_acc, v_acc
|
||||
|
||||
def cm_trx_to_stripe(cm_trx):
|
||||
cm_stripe = None
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if low <= cm_trx <= high:
|
||||
cm_stripe = name
|
||||
return cm_stripe
|
||||
|
||||
def fm_trx_to_stripe(fm_trx):
|
||||
fm_stripe = None
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + ' (flat)'
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + ' (toroid)'
|
||||
return fm_stripe
|
||||
|
||||
def mo1_energy_resolution(xtal, energy):
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
crystal = bl.mo1.material1[index]
|
||||
|
||||
dtheta = np.linspace(-30, 90, 601)
|
||||
theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6
|
||||
refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0])**2 # single crystal
|
||||
|
||||
refl2 = refl**2 # DCM with parallel crystals
|
||||
|
||||
# FWHM of the DCM curve
|
||||
spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0)
|
||||
r1, r2 = spline.roots()
|
||||
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
|
||||
|
||||
# Energy resolution
|
||||
theta_B = crystal.get_Bragg_angle(energy)
|
||||
dE_over_E = fwhm_rad / np.tan(theta_B)
|
||||
dE = dE_over_E * energy
|
||||
|
||||
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
|
||||
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
|
||||
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
|
||||
|
||||
return dE
|
||||
|
||||
def cm_reflectivity(cm_stripe, cm_pitch, energy):
|
||||
index = bl.cm.surface.index(cm_stripe)
|
||||
rs, rp = bl.cm.material[index].get_amplitude(
|
||||
energy,
|
||||
np.sin(cm_pitch)
|
||||
)[0:2]
|
||||
refl = abs(rs)**2
|
||||
return refl
|
||||
|
||||
def fm_reflectivity(fm_stripe, fm_pitch, energy):
|
||||
if fm_stripe in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
surface = bl.fm.surfaceToroid
|
||||
material = bl.fm.materialToroid
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
material = bl.fm.materialFlat
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
rs, rp = material[index].get_amplitude(
|
||||
energy,
|
||||
np.sin(fm_pitch)
|
||||
)[0:2]
|
||||
refl = abs(rs)**2
|
||||
return refl
|
||||
|
||||
def mo1_bragg_angle(mo_mode, d_spacing, energy, cm_pitch):
|
||||
H = 6.62606957E-34
|
||||
E = 1.602176634E-19
|
||||
C = 299792458
|
||||
wl = C * H / (E * energy)
|
||||
val = wl / (2 * d_spacing * 1e-10)
|
||||
bragg_angle = 0
|
||||
if val > -1 and val < 1:
|
||||
bragg_angle = np.asin(val)
|
||||
if mo_mode in 'Monochromatic':
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg_angle_cor = ((2 * cm_pitch) + bragg_angle)
|
||||
elif mo_mode in 'Pinkbeam':
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg_angle_cor = (2 * cm_pitch)
|
||||
return bragg_angle, bragg_angle_cor
|
||||
|
||||
def fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc=None, sldi_vacc=None, fm_focx=None, fm_focy=None):
|
||||
p = bl.fm.center[1] # posFM
|
||||
q = smpl - bl.fm.center[1] # dist posFM to posEX
|
||||
if fm_focus in 'Defocused':
|
||||
a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror
|
||||
b = 2 * np.tan(sldi_vacc) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam)
|
||||
x = fm_focx
|
||||
y = fm_focy
|
||||
qx = q + x * p / a
|
||||
qy = q + y * p / b
|
||||
f = (p * qx) / (p + qx) # focal length
|
||||
else: # Calculate for focused beam on sample in "manual" and "focused" mode
|
||||
qy = None
|
||||
f = (p * q) / (p + q) # focal length
|
||||
pitch = 0
|
||||
if 'Rh' in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM
|
||||
if 'Pt' in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM
|
||||
return pitch, qy
|
||||
|
||||
def cm_critical_angle(cm_stripe, energy):
|
||||
if cm_stripe in 'Si':
|
||||
stripe = bl.stripeSi
|
||||
elif cm_stripe in 'Pt':
|
||||
stripe = bl.stripePt
|
||||
elif cm_stripe in 'Rh':
|
||||
stripe = bl.stripeRh
|
||||
else:
|
||||
raise Exception(f'Stripe {stripe} not found in beamline parameters!')
|
||||
w = CHeVcm/100/energy # convert energy [eV] to wavelength [m]
|
||||
# Calculate critical angle for mirror
|
||||
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy))
|
||||
numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3)
|
||||
criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi)
|
||||
return criticalAngle
|
||||
|
||||
|
||||
def mirror_surface_geometries(mirror):
|
||||
if mirror in "cm":
|
||||
surface = bl.cm.surface
|
||||
limOptX = bl.cm.limOptX
|
||||
limOptY = bl.cm.limOptY
|
||||
elif mirror in 'fm_toroid':
|
||||
surface = bl.fm.surfaceToroid
|
||||
limOptX = bl.fm.limOptXToroid
|
||||
limOptY = bl.fm.limOptYToroid
|
||||
elif mirror in 'fm_flat':
|
||||
surface = bl.fm.surfaceFlat
|
||||
limOptX = bl.fm.limOptXFlat
|
||||
limOptY = bl.fm.limOptYFlat
|
||||
else:
|
||||
raise ValueError(f'Requested mirror {mirror} not available!')
|
||||
geom = {}
|
||||
for sf, lx, hx, ly, hy in zip(surface, limOptX[0], limOptX[1], limOptY[0], limOptY[1]):
|
||||
geom[sf] = (lx, ly, hx-lx, hy-ly)
|
||||
return geom
|
||||
|
||||
def mo_surface_geometries(mo, plane):
|
||||
if mo in 'mo1':
|
||||
xtal = bl.mo1.xtal
|
||||
xtal_width = bl.mo1.xtalWidth
|
||||
xtal_offset_x = bl.mo1.xtalOffsetX
|
||||
if plane == 0:
|
||||
xtal_length = bl.mo1.xtalLength1
|
||||
else:
|
||||
xtal_length = bl.mo1.xtalLength2
|
||||
else:
|
||||
raise ValueError(f'Requested mono {mo} not available!')
|
||||
geom = {}
|
||||
for sf, w, offx, length in zip(xtal, xtal_width, xtal_offset_x, xtal_length):
|
||||
geom[sf] = (offx-w/2, -length/2, w, length)
|
||||
return geom
|
||||
|
||||
def wall_geometries():
|
||||
geom = []
|
||||
for i, _ in enumerate(bl.walls.start):
|
||||
geom.append([
|
||||
bl.walls.start[i],
|
||||
bl.walls.height[i][0],
|
||||
bl.walls.end[i] - bl.walls.start[i],
|
||||
bl.walls.height[i][1] - bl.walls.height[i][0],
|
||||
])
|
||||
return geom
|
||||
|
||||
def pipe_geometries():
|
||||
pipes = []
|
||||
for i, _ in enumerate(bl.vacuum_pipes.center):
|
||||
top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
||||
bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
||||
pipes.append({
|
||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
'y': np.array([top, top])
|
||||
})
|
||||
pipes.append({
|
||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
'y': np.array([bottom, bottom])
|
||||
})
|
||||
return pipes
|
||||
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
Calculates the positions of axes based on a beamline config
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
import debye_bec.bec_widgets.widgets.digital_twin.x01da_parameters as bl
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]:
|
||||
"""
|
||||
Calculates the positions of axes based on a beamline config.
|
||||
|
||||
Args:
|
||||
cfg(ConfigDict): Dictionary with beamline config
|
||||
|
||||
Returns:
|
||||
dict[str, dict[str, float]]: Dictionary mapping device names to dictionaries
|
||||
containing a "value" key with the corresponding float value (position).
|
||||
"""
|
||||
|
||||
pos = {}
|
||||
|
||||
## FE slits
|
||||
trxr = -np.arctan(cfg["h_acc"]) * bl.feSlits.center1[1]
|
||||
trxw = (
|
||||
(np.arctan(cfg["h_acc"]) * bl.feSlits.center1[1])
|
||||
/ bl.feSlits.center1[1]
|
||||
* bl.feSlits.center2[1]
|
||||
)
|
||||
tryb = -np.arctan(cfg["v_acc"]) * bl.feSlits.center1[1]
|
||||
tryt = (
|
||||
(np.arctan(cfg["v_acc"]) * bl.feSlits.center1[1])
|
||||
/ bl.feSlits.center1[1]
|
||||
* bl.feSlits.center2[1]
|
||||
)
|
||||
|
||||
xgap = trxw - trxr
|
||||
ygap = tryt - tryb
|
||||
|
||||
pos["sldi_gapx"] = {"value": xgap}
|
||||
pos["sldi_gapy"] = {"value": ygap}
|
||||
|
||||
## Collimating Mirror
|
||||
obj_dist = bl.cm.center[1] # object distance
|
||||
beam_vs = 2 * obj_dist * np.tan(cfg["v_acc"]) # vertical size of beam after CM
|
||||
|
||||
# TRX
|
||||
if cfg["cm_stripe"] in bl.cm.surface:
|
||||
index = bl.cm.surface.index(cfg["cm_stripe"])
|
||||
else:
|
||||
raise ValueError(f"Requested stripe {cfg['cm_stripe']} not found in parameters!")
|
||||
cm_trx = -(bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
||||
pos["cm_trx"] = {"value": cm_trx}
|
||||
|
||||
# TRY
|
||||
height = obj_dist * np.tan(cfg["v_acc"]) ** 2 * 1 / np.tan(cfg["cm_pitch"])
|
||||
pos["cm_try"] = {"value": height}
|
||||
|
||||
# Pitch
|
||||
pos["cm_rotx"] = {
|
||||
"value": -cfg["cm_pitch"] * 1e3
|
||||
} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
# Bending Radius
|
||||
radius = (
|
||||
2.0 * obj_dist / np.sin(cfg["cm_pitch"])
|
||||
) # Elements of modern X-ray Physics, page 108 ff.
|
||||
pos["cm_bnd_radius"] = {"value": radius * 1e-6} # Convert to km
|
||||
|
||||
## Monochromator
|
||||
if cfg["mo1_mode"] == "Monochromatic":
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg = cfg["mo1_bragg"]
|
||||
elif cfg["mo1_mode"] == "Pinkbeam":
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg = 0
|
||||
else:
|
||||
raise ValueError("Monochromator mode not supported")
|
||||
pos["mo1_bragg_angle"] = {"value": bragg / np.pi * 180} # Bragg angle in deg
|
||||
|
||||
# TRY, Height
|
||||
l = bl.mo1.xtalGap[0] / np.sin(cfg["mo1_bragg"])
|
||||
yhor = l * np.cos(2.0 * (cfg["mo1_bragg"] + cfg["cm_pitch"]))
|
||||
yver = yhor * np.tan(2.0 * cfg["cm_pitch"])
|
||||
|
||||
if cfg["mo1_mode"] == "Monochromatic":
|
||||
beam_offset_mo1 = (
|
||||
l * np.sin(2.0 * (cfg["mo1_bragg"] + cfg["cm_pitch"])) - yver
|
||||
) # Resultat ist korrekt!
|
||||
elif cfg["mo1_mode"] == "Pinkbeam":
|
||||
beam_offset_mo1 = 0
|
||||
else:
|
||||
raise ValueError("Monochromator mode not supported")
|
||||
|
||||
def csc(a):
|
||||
return 1 / np.sin(a)
|
||||
|
||||
def cot(a):
|
||||
return 1 / np.tan(a)
|
||||
|
||||
# calculate height of center of first crystal surface
|
||||
f = bl.mo1.rotOffset # rotation offset, mm
|
||||
d = bl.mo1.heightOffset # xtal height offset, mm
|
||||
c = d * csc(cfg["mo1_bragg"]) - f * cot(cfg["mo1_bragg"])
|
||||
|
||||
# Calculate height of center of rotation
|
||||
b = np.sqrt(
|
||||
d**2 * csc(cfg["mo1_bragg"]) ** 2
|
||||
- 2 * d * f * cot(cfg["mo1_bragg"]) * csc(cfg["mo1_bragg"])
|
||||
+ f**2 * cot(cfg["mo1_bragg"]) ** 2
|
||||
+ f**2
|
||||
)
|
||||
h = np.cos(np.pi / 2 - np.arctan(f / c) - cfg["mo1_bragg"] - 2 * cfg["cm_pitch"]) * b
|
||||
h2 = ((bl.mo1.center[1] - bl.cm.center[1]) - np.sqrt(b**2 - h**2)) * np.tan(2 * cfg["cm_pitch"])
|
||||
height_mo1_real = (
|
||||
h + h2
|
||||
) # per design, the height should not change if the pitch of the CM is not changed!
|
||||
if cfg["mo1_mode"] == "Monochromatic":
|
||||
pass
|
||||
elif cfg["mo1_mode"] == "Pinkbeam":
|
||||
height_mo1_real = (
|
||||
height_mo1_real - 13
|
||||
) # Move down to let beam pass between both crystal without touching copper cooler
|
||||
else:
|
||||
raise ValueError("Monochromator mode not supported")
|
||||
pos["mo1_try"] = {"value": height_mo1_real}
|
||||
|
||||
# TRX, Crystal selection
|
||||
if cfg["mo1_mode"] == "Monochromatic":
|
||||
xtal = cfg["mo1_xtal"].translate(
|
||||
str.maketrans("", "", "()")
|
||||
) # Remove brackets from xtal name to conform with parameters
|
||||
if xtal in bl.mo1.xtal:
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
else:
|
||||
raise ValueError(f"Requested xtal {xtal} not found in parameters!")
|
||||
pos["mo1_trx"] = {"value": bl.mo1.xtalOffsetX[index]}
|
||||
else:
|
||||
pos["mo1_trx"] = {"value": 0}
|
||||
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg["mo1_bragg"]) # Calculations for Mono
|
||||
dz = diag * np.cos(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"]))
|
||||
|
||||
## Slits 1
|
||||
d = bl.opSlits1.center[1] - bl.cm.center[1] - dz
|
||||
sl1_beam_height = d * np.tan(2 * cfg["cm_pitch"]) + beam_offset_mo1
|
||||
pos["sl1_centery"] = {"value": sl1_beam_height}
|
||||
pos["sl1_gapy"] = {"value": beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 1
|
||||
d = bl.opBM1.center[1] - bl.cm.center[1] - dz
|
||||
bm1_beam_height = d * np.tan(2 * cfg["cm_pitch"]) + beam_offset_mo1
|
||||
pos["bm1_try"] = {"value": bm1_beam_height}
|
||||
|
||||
## Focusing Mirror
|
||||
p = bl.fm.center[1]
|
||||
q = cfg["smpl"] - bl.fm.center[1]
|
||||
f = (p * q) / (p + q) # focal length
|
||||
|
||||
# Bender radius
|
||||
if cfg["fm_qy"] is None:
|
||||
radius = 2 * q / np.sin(cfg["fm_rotx"]) # ideal bending radius for focused beam
|
||||
else:
|
||||
radius = (
|
||||
2 * cfg["fm_qy"] / np.sin(cfg["fm_rotx"])
|
||||
) # ideal bending radius for unfocused beam
|
||||
pos["fm_bnd_radius"] = {"value": radius * 1e-6} # Convert to km
|
||||
|
||||
# Pitch
|
||||
d = bl.fm.center[1] - bl.cm.center[1] - dz
|
||||
fm_rotx = (
|
||||
2 * cfg["cm_pitch"] - cfg["fm_rotx"]
|
||||
) # calculate pitch in absolute values (according to horizontal plane)
|
||||
pos["fm_rotx"] = {
|
||||
"value": -fm_rotx * 1e3
|
||||
} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
if cfg["fm_stripe"] in ("Rh (toroid)", "Pt (toroid)"):
|
||||
|
||||
# TRY
|
||||
if cfg["fm_stripe"] in "Rh (toroid)":
|
||||
r = bl.fm.r[0]
|
||||
h_cyl = bl.fm.hToroid[0]
|
||||
else: # PT toroid
|
||||
r = bl.fm.r[1]
|
||||
h_cyl = bl.fm.hToroid[1]
|
||||
width_beam = 2 * bl.fm.center[1] * np.tan(cfg["h_acc"] * 1e-3)
|
||||
alpha = np.arccos(1 - width_beam**2 / (2 * r**2))
|
||||
h = r - (r * np.cos(alpha / 2))
|
||||
fm_beam_height = (d * np.tan(2 * cfg["cm_pitch"]) + beam_offset_mo1) * cfg["fm_gain_height"]
|
||||
fm_height = (d * np.tan(2 * cfg["cm_pitch"]) + beam_offset_mo1 - h_cyl + h / 2) * cfg[
|
||||
"fm_gain_height"
|
||||
]
|
||||
pos["fm_try"] = {"value": fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg["fm_stripe"] in "Rh (toroid)":
|
||||
x_cyl = -bl.fm.xToroid[0]
|
||||
else:
|
||||
x_cyl = -bl.fm.xToroid[1]
|
||||
pos["fm_trx"] = {"value": x_cyl}
|
||||
|
||||
elif cfg["fm_stripe"] in ("Rh (flat)", "Pt (flat)"):
|
||||
|
||||
# TRY
|
||||
fm_height = (d * np.tan(2 * cfg["cm_pitch"]) + beam_offset_mo1) * cfg["fm_gain_height"]
|
||||
fm_beam_height = fm_height
|
||||
pos["fm_try"] = {"value": fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg["fm_stripe"] in "Rh (flat)":
|
||||
x_flat = -bl.fm.xFlat[0]
|
||||
else:
|
||||
x_flat = -bl.fm.xFlat[1]
|
||||
pos["fm_trx"] = {"value": x_flat}
|
||||
|
||||
else:
|
||||
raise ValueError("FM Stripe selection not valid")
|
||||
|
||||
pos["fm_roty"] = {"value": 0}
|
||||
pos["fm_rotz"] = {"value": 0}
|
||||
|
||||
## Slits 2
|
||||
d = bl.opSlits2.center[1] - bl.fm.center[1]
|
||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg["cm_pitch"] - 2 * cfg["fm_rotx"]))
|
||||
pos["sl2_centery"] = {"value": sl2_beam_height}
|
||||
pos["sl2_gapy"] = {"value": beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 2
|
||||
d = bl.opBM2.center[1] - bl.fm.center[1]
|
||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg["cm_pitch"] - 2 * cfg["fm_rotx"]))
|
||||
pos["bm2_try"] = {"value": bm2_beam_height}
|
||||
|
||||
## Optical Table
|
||||
|
||||
# TRY
|
||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg["cm_pitch"] - 2 * cfg["fm_rotx"]))
|
||||
pos["ot_try"] = {"value": ot_height}
|
||||
|
||||
# Pitch
|
||||
ot_pitch = -(2 * cfg["cm_pitch"] - 2 * cfg["fm_rotx"])
|
||||
pos["ot_rotx"] = {"value": ot_pitch * 1e3}
|
||||
|
||||
# TRZ ES1
|
||||
ot_es1_trz = cfg["smpl"]
|
||||
pos["ot_es1_trz"] = {"value": ot_es1_trz}
|
||||
|
||||
# ES0 exit window
|
||||
pos["es0wi_try"] = {
|
||||
"value": 5
|
||||
} # At 5mm, the middle of the window is 500 mm from the table (neutral position)
|
||||
|
||||
return pos
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Calculates the sideview coordinates based on a beamline config.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import debye_bec.bec_widgets.widgets.digital_twin.x01da_parameters as bl
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict, DataDict
|
||||
|
||||
|
||||
def calc_sideview(cfg: ConfigDict) -> DataDict:
|
||||
"""
|
||||
Calculates the sideview coordinates based on a beamline config.
|
||||
|
||||
Args:
|
||||
cfg(ConfigDict): Dictionary with beamline config
|
||||
|
||||
Returns:
|
||||
DataDict: Sideview data
|
||||
"""
|
||||
|
||||
beam: DataDict = {"x": [], "y": []}
|
||||
|
||||
beam["x"] = []
|
||||
beam["y"] = []
|
||||
beam["x"].append(0) # Source
|
||||
beam["y"].append(bl.sourceHeight)
|
||||
beam["x"].append(bl.cm.center[1]) # CM
|
||||
beam["y"].append(bl.sourceHeight)
|
||||
if cfg["mo1_mode"] in "Monochromatic":
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg["mo1_bragg"]) # Calculations for Mono
|
||||
dy = diag * np.sin(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"]))
|
||||
dz = diag * np.cos(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"]))
|
||||
beam["x"].append(bl.mo1.center[1] - dz / 2) # Mono 1.1
|
||||
beam["y"].append(
|
||||
bl.sourceHeight
|
||||
+ np.tan(2 * cfg["cm_pitch"]) * (bl.mo1.center[1] - dz / 2 - bl.cm.center[1])
|
||||
)
|
||||
beam["x"].append(bl.mo1.center[1] + dz / 2) # Mono 1.2
|
||||
beam["y"].append(
|
||||
bl.sourceHeight
|
||||
+ np.tan(2 * cfg["cm_pitch"]) * (bl.mo1.center[1] - dz / 2 - bl.cm.center[1])
|
||||
+ dy
|
||||
)
|
||||
beam["x"].append(bl.fm.center[1]) # FM
|
||||
beam["y"].append(
|
||||
bl.sourceHeight
|
||||
+ np.tan(2 * cfg["cm_pitch"]) * (bl.fm.center[1] - bl.cm.center[1] - dz)
|
||||
+ dy
|
||||
)
|
||||
beam["x"].append(cfg["smpl"]) # Experiment
|
||||
beam["y"].append(
|
||||
bl.sourceHeight
|
||||
+ np.tan(2 * cfg["cm_pitch"]) * (bl.fm.center[1] - bl.cm.center[1] - dz)
|
||||
+ dy
|
||||
+ np.tan(2 * (cfg["cm_pitch"] - cfg["fm_rotx"])) * (cfg["smpl"] - bl.fm.center[1])
|
||||
)
|
||||
elif cfg["mo1_mode"] == "Pinkbeam":
|
||||
beam["x"].append(bl.fm.center[1]) # FM
|
||||
beam["y"].append(
|
||||
bl.sourceHeight + np.tan(2 * cfg["cm_pitch"]) * (bl.fm.center[1] - bl.cm.center[1])
|
||||
)
|
||||
beam["x"].append(cfg["smpl"]) # Experiment
|
||||
beam["y"].append(
|
||||
bl.sourceHeight
|
||||
+ np.tan(2 * cfg["cm_pitch"]) * (bl.fm.center[1] - bl.cm.center[1])
|
||||
+ np.tan(2 * (cfg["cm_pitch"] - cfg["fm_rotx"])) * (cfg["smpl"] - bl.fm.center[1])
|
||||
)
|
||||
|
||||
return beam
|
||||
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Calculates the surface coordinates based on a beamline config.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
import debye_bec.bec_widgets.widgets.digital_twin.x01da_parameters as bl
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict, SurfaceDict
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
def calc_surfaces(cfg: ConfigDict) -> SurfaceDict:
|
||||
"""
|
||||
Calculates the surface coordinates based on a beamline config.
|
||||
|
||||
Args:
|
||||
cfg(ConfigDict): Dictionary with beamline config
|
||||
|
||||
Returns:
|
||||
SurfaceDict: Surface data
|
||||
"""
|
||||
|
||||
out: SurfaceDict = {
|
||||
"cm": {"x": [], "y": []},
|
||||
"mo1_1": {"x": [], "y": []},
|
||||
"mo1_2": {"x": [], "y": []},
|
||||
"fm": {"x": [], "y": []},
|
||||
}
|
||||
|
||||
# Collimating mirror
|
||||
l = 2 * bl.cm.center[1] * np.tan(cfg["v_acc"]) / np.sin(cfg["cm_pitch"])
|
||||
|
||||
w1 = 2 * (bl.cm.center[1] - l / 2) * np.tan(cfg["h_acc"])
|
||||
w2 = 2 * (bl.cm.center[1] + l / 2) * np.tan(cfg["h_acc"])
|
||||
|
||||
index = bl.cm.surface.index(cfg["cm_stripe"])
|
||||
|
||||
cen = -cfg["cm_trx"]
|
||||
|
||||
out["cm"]["x"] = [cen - w1 / 2, cen - w2 / 2, cen + w2 / 2, cen + w1 / 2]
|
||||
out["cm"]["y"] = [-l / 2, l / 2, l / 2, -l / 2]
|
||||
|
||||
# Monochromator
|
||||
# calculate height of center of first crystal surface
|
||||
c = bl.mo1.heightOffset * 1 / np.sin(cfg["mo1_bragg"]) - bl.mo1.rotOffset * 1 / np.tan(
|
||||
cfg["mo1_bragg"]
|
||||
)
|
||||
e = bl.mo1.xtalGap[0] / np.tan(cfg["mo1_bragg"]) - c
|
||||
|
||||
xtal = cfg["mo1_xtal"].translate(
|
||||
str.maketrans("", "", "()")
|
||||
) # Remove brackets from xtal name to conform with parameters
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
|
||||
xtal_pos = bl.mo1.xtalOffsetX[index]
|
||||
xtal_length_1 = bl.mo1.xtalLength1[index]
|
||||
xtal_length_2 = bl.mo1.xtalLength2[index]
|
||||
|
||||
width_beam = 2 * bl.mo1.center[1] * np.tan(cfg["h_acc"])
|
||||
|
||||
height_beam = 2 * bl.cm.center[1] * np.tan(cfg["v_acc"])
|
||||
w = height_beam / np.sin(cfg["mo1_bragg"])
|
||||
|
||||
if cfg["mo1_mode"] in "Monochromatic":
|
||||
out["mo1_1"]["x"] = [
|
||||
xtal_pos - width_beam / 2,
|
||||
xtal_pos + width_beam / 2,
|
||||
xtal_pos + width_beam / 2,
|
||||
xtal_pos - width_beam / 2,
|
||||
]
|
||||
out["mo1_1"]["y"] = [
|
||||
xtal_length_1 / 2 - c - w / 2,
|
||||
xtal_length_1 / 2 - c - w / 2,
|
||||
xtal_length_1 / 2 - c + w / 2,
|
||||
xtal_length_1 / 2 - c + w / 2,
|
||||
]
|
||||
out["mo1_2"]["x"] = [
|
||||
xtal_pos - width_beam / 2,
|
||||
xtal_pos + width_beam / 2,
|
||||
xtal_pos + width_beam / 2,
|
||||
xtal_pos - width_beam / 2,
|
||||
]
|
||||
out["mo1_2"]["y"] = [
|
||||
-xtal_length_2 / 2 + e - w / 2,
|
||||
-xtal_length_2 / 2 + e - w / 2,
|
||||
-xtal_length_2 / 2 + e + w / 2,
|
||||
-xtal_length_2 / 2 + e + w / 2,
|
||||
]
|
||||
else: # Pinkbeam
|
||||
out["mo1_1"]["x"] = []
|
||||
out["mo1_1"]["y"] = []
|
||||
out["mo1_2"]["x"] = []
|
||||
out["mo1_2"]["y"] = []
|
||||
|
||||
# Focusing mirror
|
||||
if cfg["fm_stripe"] in ("Rh (toroid)", "Pt (toroid)"):
|
||||
surface = bl.fm.surfaceToroid
|
||||
stripe = re.sub(r"\s*\(.*?\)", "", cfg["fm_stripe"]).strip()
|
||||
index = surface.index(stripe)
|
||||
r = bl.fm.r[index]
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
stripe = re.sub(r"\s*\(.*?\)", "", cfg["fm_stripe"]).strip()
|
||||
index = surface.index(stripe)
|
||||
r = bl.fm.r[index]
|
||||
off = -cfg["fm_trx"]
|
||||
|
||||
width_beam = 2 * bl.fm.center[1] * np.tan(cfg["h_acc"])
|
||||
|
||||
if cfg["fm_stripe"] in ("Rh (toroid)", "Pt (toroid)"):
|
||||
|
||||
l = height_beam / np.sin(cfg["fm_rotx"])
|
||||
alpha = np.arccos(1 - width_beam**2 / (2 * r**2))
|
||||
h = r - (r * np.cos(alpha / 2))
|
||||
z = h / np.tan(cfg["fm_rotx"])
|
||||
|
||||
x = [off - width_beam / 2, off - width_beam / 2]
|
||||
y = [l / 2 - z / 2, -l / 2 - z / 2]
|
||||
|
||||
res = 20
|
||||
x_elipse = np.linspace(0, np.pi, res)
|
||||
y_elipse = np.linspace(0, np.pi, res)
|
||||
x_elipse = [-width_beam / 2 * np.cos(i) + off for i in x_elipse]
|
||||
y_elipse = [width_beam * np.sin(i) * z / width_beam - l / 2 - z / 2 for i in y_elipse]
|
||||
|
||||
x.extend(x_elipse)
|
||||
y.extend(y_elipse)
|
||||
|
||||
x.extend([off + width_beam / 2, off + width_beam / 2])
|
||||
y.extend([-l / 2 - z / 2, l / 2 - z / 2])
|
||||
|
||||
res = 50
|
||||
x_elipse = np.linspace(np.pi, 0, res)
|
||||
y_elipse = np.linspace(np.pi, 0, res)
|
||||
x_elipse = [-width_beam / 2 * np.cos(i) + off for i in x_elipse]
|
||||
y_elipse = [width_beam * np.sin(i) * z / width_beam + l / 2 - z / 2 for i in y_elipse]
|
||||
|
||||
x.extend(x_elipse)
|
||||
y.extend(y_elipse)
|
||||
|
||||
out["fm"]["x"] = x
|
||||
out["fm"]["y"] = y
|
||||
|
||||
else: # flat surface, no toroid
|
||||
l = height_beam / np.sin(cfg["fm_rotx"])
|
||||
|
||||
w1 = 2 * (bl.fm.center[1] - l / 2) * np.tan(cfg["h_acc"])
|
||||
w2 = 2 * (bl.fm.center[1] + l / 2) * np.tan(cfg["h_acc"])
|
||||
|
||||
out["fm"]["x"] = [off - w1 / 2, off + w1 / 2, off + w2 / 2, off - w2 / 2]
|
||||
out["fm"]["y"] = [-l / 2, -l / 2, l / 2, l / 2]
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,418 @@
|
||||
"""
|
||||
Various calculations for the digital twin
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Literal, cast
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from scipy.interpolate import UnivariateSpline
|
||||
from xrt.backends.raycing.physconsts import AVOGADRO, CHeVcm
|
||||
|
||||
import debye_bec.bec_widgets.widgets.digital_twin.x01da_parameters as bl
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
H = 6.62606957e-34
|
||||
E = 1.602176634e-19
|
||||
C = 299792458
|
||||
RE = 2.8179e-15
|
||||
|
||||
|
||||
def sldi_gap_to_acc(sldi_gapx: float, sldi_gapy: float) -> tuple[float, float]:
|
||||
"""
|
||||
Calculate the slits acceptance based on the gap values
|
||||
|
||||
Args:
|
||||
sldi_gapx(float): GAPX value of the slits in mm
|
||||
sldi_gapy(float): GAPY value of the slits in mm
|
||||
|
||||
Returns:
|
||||
tuple[float, float]: Horizontal and vertical acceptance in rad
|
||||
"""
|
||||
d1 = bl.feSlits.center1[1]
|
||||
d2 = bl.feSlits.center2[1]
|
||||
h_acc = np.tan(sldi_gapx / (d2 + d1))
|
||||
v_acc = np.tan(sldi_gapy / (d2 + d1))
|
||||
return h_acc, v_acc
|
||||
|
||||
|
||||
def cm_trx_to_stripe(cm_trx: float) -> str | None:
|
||||
"""
|
||||
Based on the trx value of the collimating mirror, return
|
||||
the correct stripe
|
||||
|
||||
Args:
|
||||
cm_trx(float): Collimating mirror trx value
|
||||
|
||||
Returns
|
||||
str | None: Stripe of the mirror, None if not found
|
||||
"""
|
||||
cm_stripe = None
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if low <= cm_trx <= high:
|
||||
cm_stripe = name
|
||||
return cm_stripe
|
||||
|
||||
|
||||
def cm_stripe_to_trx(cm_stripe: str) -> float | None:
|
||||
"""
|
||||
Based on the stripe of the collimating mirror, return
|
||||
the trx value
|
||||
|
||||
Args:
|
||||
cm_stripe(str): Stripe of the collimating mirror
|
||||
|
||||
Returns:
|
||||
float | None: TRX value of the stripe. None if not found
|
||||
"""
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if cm_stripe == name:
|
||||
return -(low + high) / 2
|
||||
return None
|
||||
|
||||
|
||||
def fm_trx_to_stripe(fm_trx: float) -> str | None:
|
||||
"""
|
||||
Based on the trx value of the focusing mirror, return
|
||||
the correct stripe
|
||||
|
||||
Args:
|
||||
fm_trx(float): focusing mirror trx value
|
||||
|
||||
Returns
|
||||
str | None: Stripe of the mirror, None if not found
|
||||
"""
|
||||
fm_stripe = None
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + " (flat)"
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + " (toroid)"
|
||||
return fm_stripe
|
||||
|
||||
|
||||
def fm_stripe_to_trx(fm_stripe: str) -> float | None:
|
||||
"""
|
||||
Based on the stripe of the focusing mirror, return
|
||||
the trx value
|
||||
|
||||
Args:
|
||||
fm_stripe(str): Stripe of the focusing mirror
|
||||
|
||||
Returns:
|
||||
float | None: TRX value of the stripe. None if not found
|
||||
"""
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if fm_stripe == name + " (flat)":
|
||||
return (low + high) / 2
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if fm_stripe == name + " (toroid)":
|
||||
return -(low + high) / 2
|
||||
return None
|
||||
|
||||
|
||||
def mo1_energy_resolution(xtal: Literal["Si111", "Si311"], energy: float) -> float:
|
||||
"""
|
||||
Calculate the energy resolution of the monochromator
|
||||
|
||||
Args:
|
||||
xtal(str): Xtal name. "Si111" or "Si311"
|
||||
energy(float): Energy in eV
|
||||
|
||||
Returns:
|
||||
float: Energy resolution in eV
|
||||
"""
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
crystal = bl.mo1.material1[index]
|
||||
|
||||
dtheta = np.linspace(-30, 90, 601)
|
||||
theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6
|
||||
refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0]) ** 2 # single crystal
|
||||
|
||||
refl2 = refl**2 # DCM with parallel crystals
|
||||
|
||||
# FWHM of the DCM curve
|
||||
spline = UnivariateSpline(dtheta, refl2 - refl2.max() / 2, s=0)
|
||||
roots = cast(np.ndarray, spline.roots())
|
||||
r1, r2 = float(roots[0]), float(roots[1])
|
||||
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
|
||||
|
||||
# Energy resolution
|
||||
theta_b = crystal.get_Bragg_angle(energy)
|
||||
de_over_e = fwhm_rad / np.tan(theta_b)
|
||||
de = de_over_e * energy
|
||||
|
||||
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
|
||||
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
|
||||
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
|
||||
|
||||
return de
|
||||
|
||||
|
||||
def cm_reflectivity(cm_stripe: str, cm_pitch: float, energy: float) -> float:
|
||||
"""
|
||||
Calculate the reflectivity of the mirror stripe based
|
||||
on the pitch and energy.
|
||||
|
||||
Args:
|
||||
cm_stripe(str): Mirror stripe
|
||||
cm_pitch(float): Pitch of the mirror (beam incidence angle)
|
||||
energy(float): Energy of the beam in eV
|
||||
|
||||
Returns:
|
||||
float: Reflectivity [0-1]
|
||||
"""
|
||||
index = bl.cm.surface.index(cm_stripe)
|
||||
rs, _ = bl.cm.material[index].get_amplitude(energy, np.sin(cm_pitch))[0:2]
|
||||
refl = abs(rs) ** 2
|
||||
return refl
|
||||
|
||||
|
||||
def fm_reflectivity(fm_stripe: str, fm_pitch: float, energy: float) -> float:
|
||||
"""
|
||||
Calculate the reflectivity of the mirror stripe based
|
||||
on the pitch and energy.
|
||||
|
||||
Args:
|
||||
cm_stripe(str): Mirror stripe
|
||||
cm_pitch(float): Pitch of the mirror (beam incidence angle)
|
||||
energy(float): Energy of the beam in eV
|
||||
|
||||
Returns:
|
||||
float: Reflectivity [0-1]
|
||||
"""
|
||||
if fm_stripe in ("Rh (toroid)", "Pt (toroid)"):
|
||||
surface = bl.fm.surfaceToroid
|
||||
material = bl.fm.materialToroid
|
||||
stripe = re.sub(r"\s*\(.*?\)", "", fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
material = bl.fm.materialFlat
|
||||
stripe = re.sub(r"\s*\(.*?\)", "", fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
rs, _ = material[index].get_amplitude(energy, np.sin(fm_pitch))[0:2]
|
||||
refl = abs(rs) ** 2
|
||||
return refl
|
||||
|
||||
|
||||
def mo1_bragg_angle(
|
||||
mo_mode: Literal["Monochromatic", "Pinkbeam"], d_spacing: float, energy: float, cm_pitch: float
|
||||
) -> tuple[float, float]:
|
||||
"""
|
||||
Calculate the bragg angle of the monochromator.
|
||||
Corrects for the collimating mirror pitch.
|
||||
|
||||
Args:
|
||||
mo_mode(str): Monochromator mode. "Monochromatic" or "Pinkbeam"
|
||||
d_spacing(float): D-spacing of the crystal in Angstrom
|
||||
energy(float): Energy of the beam in eV
|
||||
cm_pitch(float): Pitch of collimating mirror in rad
|
||||
|
||||
Returns:
|
||||
tuple[float, float]: Bragg angle and corrected bragg angle
|
||||
"""
|
||||
wl = C * H / (E * energy)
|
||||
val = wl / (2 * d_spacing * 1e-10)
|
||||
bragg_angle = 0
|
||||
if val > -1 and val < 1:
|
||||
bragg_angle = np.asin(val)
|
||||
if mo_mode == "Monochromatic":
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg_angle_cor = (2 * cm_pitch) + bragg_angle
|
||||
else:
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg_angle_cor = 2 * cm_pitch
|
||||
return bragg_angle, bragg_angle_cor
|
||||
|
||||
|
||||
def fm_ideal_pitch(
|
||||
fm_focus: Literal["Defocused", "Focused", "Manual"],
|
||||
fm_stripe: str,
|
||||
smpl: float,
|
||||
sldi_hacc: float | None = None,
|
||||
sldi_vacc: float | None = None,
|
||||
fm_focx: float | None = None,
|
||||
fm_focy: float | None = None,
|
||||
) -> tuple[float, float | None]:
|
||||
"""
|
||||
Calculates the ideal pitch for the focusing mirror depending on the
|
||||
focusing strategy.
|
||||
If "Defocused" is chosed, sldi_hacc, sldi_vacc, fm_focx and fm_focy
|
||||
must be provided.
|
||||
|
||||
Args:
|
||||
fm_focus(str): Focus strategy. "Defocused", "Focused" or "Manual
|
||||
fm_stripe(str): Mirror stripe
|
||||
smpl(float): Sample position in mm from source
|
||||
sldi_hacc(float): Horizontal acceptance of frontend slits. Defaults to None
|
||||
sldi_vacc(float): Vertical acceptance of frontend slits. Defaults to None
|
||||
fm_focx(float): Requested horizontal spot size in mm. Defaults to None
|
||||
fm_focy(float): Requested vertical spot size in mm. Defaults to None
|
||||
|
||||
Returns:
|
||||
tuple[float, float | None]: Pitch of mirror in rad, qy in mm
|
||||
"""
|
||||
p = bl.fm.center[1] # posFM
|
||||
q = smpl - bl.fm.center[1] # dist posFM to posEX
|
||||
if fm_focus in "Defocused":
|
||||
assert sldi_hacc is not None, "sldi_hacc must be provided for Defocused mode"
|
||||
assert sldi_vacc is not None, "sldi_vacc must be provided for Defocused mode"
|
||||
assert fm_focx is not None, "fm_focx must be provided for Defocused mode"
|
||||
assert fm_focy is not None, "fm_focy must be provided for Defocused mode"
|
||||
a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror
|
||||
b = (
|
||||
2 * np.tan(sldi_vacc) * bl.cm.center[1]
|
||||
) # Beam height at focusing mirror (collimated beam)
|
||||
x = fm_focx
|
||||
y = fm_focy
|
||||
qx = q + x * p / a
|
||||
qy = q + y * p / b
|
||||
f = (p * qx) / (p + qx) # focal length
|
||||
else: # Calculate for focused beam on sample in "manual" and "focused" mode
|
||||
qy = None
|
||||
f = (p * q) / (p + q) # focal length
|
||||
pitch = 0
|
||||
if "Rh" in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[0] / (2 * f)) # ideal pitch for FM
|
||||
if "Pt" in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[1] / (2 * f)) # ideal pitch for FM
|
||||
return pitch, qy
|
||||
|
||||
|
||||
def cm_critical_angle(cm_stripe: Literal["Si", "Pt", "Rh"], energy) -> float:
|
||||
"""
|
||||
Calculate the critical angle of the mirror stripe
|
||||
|
||||
Args:
|
||||
cm_stripe(str): Mirror stripe. "Si", "Pt" or "Rh"
|
||||
energy(float): Energy in eV
|
||||
|
||||
Returns:
|
||||
float: Critical angle in rad
|
||||
"""
|
||||
if cm_stripe in "Si":
|
||||
stripe = bl.stripeSi
|
||||
elif cm_stripe in "Pt":
|
||||
stripe = bl.stripePt
|
||||
else:
|
||||
stripe = bl.stripeRh
|
||||
w = CHeVcm / 100 / energy # convert energy [eV] to wavelength [m]
|
||||
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy))
|
||||
number_density = stripe.rho * 1e3 * AVOGADRO / (stripe.elements[0].mass / 1e3)
|
||||
critical_angle = np.sqrt(number_density * RE * w**2 * f1 / np.pi)
|
||||
return critical_angle
|
||||
|
||||
|
||||
def mirror_surface_geometries(
|
||||
mirror: Literal["cm", "fm_toroid", "fm_flat"],
|
||||
) -> dict[str, tuple[float, float, float, float]]:
|
||||
"""
|
||||
Return the mirror stripe geometries
|
||||
|
||||
Args:
|
||||
mirror(str): Mirror. "cm", "fm_toroid" or "fm_flat"
|
||||
|
||||
Returns:
|
||||
dict[str, tuple[float, float, float, float]]: Dictionary mapping surface
|
||||
names to tuples of (x, y, width, height).
|
||||
"""
|
||||
if mirror in "cm":
|
||||
surface = bl.cm.surface
|
||||
lim_opt_x = bl.cm.limOptX
|
||||
lim_opt_y = bl.cm.limOptY
|
||||
elif mirror in "fm_toroid":
|
||||
surface = bl.fm.surfaceToroid
|
||||
lim_opt_x = bl.fm.limOptXToroid
|
||||
lim_opt_y = bl.fm.limOptYToroid
|
||||
elif mirror in "fm_flat":
|
||||
surface = bl.fm.surfaceFlat
|
||||
lim_opt_x = bl.fm.limOptXFlat
|
||||
lim_opt_y = bl.fm.limOptYFlat
|
||||
else:
|
||||
raise ValueError(f"Requested mirror {mirror} not available!")
|
||||
geom = {}
|
||||
for sf, lx, hx, ly, hy in zip(surface, lim_opt_x[0], lim_opt_x[1], lim_opt_y[0], lim_opt_y[1]):
|
||||
geom[sf] = (lx, ly, hx - lx, hy - ly)
|
||||
return geom
|
||||
|
||||
|
||||
def mo_surface_geometries(
|
||||
mo: Literal["mo1"], plane: Literal[0, 1]
|
||||
) -> dict[str, tuple[float, float, float, float]]:
|
||||
"""
|
||||
Return the monochromator xtal geometries
|
||||
|
||||
Args:
|
||||
mo(str): Monochromator. Only "mo1" implemented
|
||||
plane(int): Surface of xtal. 0 and 1 (First and second)
|
||||
|
||||
Returns:
|
||||
dict[str, tuple[float, float, float, float]]: Dictionary mapping surface
|
||||
names to tuples of (x, y, width, height).
|
||||
"""
|
||||
if mo in "mo1":
|
||||
xtal = bl.mo1.xtal
|
||||
xtal_width = bl.mo1.xtalWidth
|
||||
xtal_offset_x = bl.mo1.xtalOffsetX
|
||||
if plane == 0:
|
||||
xtal_length = bl.mo1.xtalLength1
|
||||
else:
|
||||
xtal_length = bl.mo1.xtalLength2
|
||||
else:
|
||||
return {}
|
||||
geom = {}
|
||||
for sf, w, offx, length in zip(xtal, xtal_width, xtal_offset_x, xtal_length):
|
||||
geom[sf] = (offx - w / 2, -length / 2, w, length)
|
||||
return geom
|
||||
|
||||
|
||||
def wall_geometries() -> list[list[float]]:
|
||||
"""
|
||||
Return the wall geometries
|
||||
|
||||
Returns:
|
||||
list[list[float]]: List of [x, y, width, height] geometry values for each wall.
|
||||
"""
|
||||
geom = []
|
||||
for i, _ in enumerate(bl.walls.start):
|
||||
geom.append(
|
||||
[
|
||||
bl.walls.start[i],
|
||||
bl.walls.height[i][0],
|
||||
bl.walls.end[i] - bl.walls.start[i],
|
||||
bl.walls.height[i][1] - bl.walls.height[i][0],
|
||||
]
|
||||
)
|
||||
return geom
|
||||
|
||||
|
||||
def pipe_geometries() -> list[dict[str, np.ndarray]]:
|
||||
"""
|
||||
Return the wall geometries
|
||||
|
||||
Returns:
|
||||
list[dict[str, np.ndarray]]: List of dictionaries with keys "x" and "y",
|
||||
each containing a numpy array of two float values representing
|
||||
the start and end coordinates of the pipe top and bottom edges.
|
||||
"""
|
||||
pipes = []
|
||||
for i, _ in enumerate(bl.vacuum_pipes.center):
|
||||
top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i] / 2 + bl.sourceHeight
|
||||
bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i] / 2 + bl.sourceHeight
|
||||
pipes.append(
|
||||
{
|
||||
"x": np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
"y": np.array([top, top]),
|
||||
}
|
||||
)
|
||||
pipes.append(
|
||||
{
|
||||
"x": np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
"y": np.array([bottom, bottom]),
|
||||
}
|
||||
)
|
||||
return pipes
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Panel for user inputs of the digital twin widget
|
||||
"""
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
|
||||
Button,
|
||||
ComboBox,
|
||||
Group,
|
||||
InputNumberField,
|
||||
NumberIndicator,
|
||||
)
|
||||
|
||||
|
||||
class InputPanel(QWidget):
|
||||
"""Panel for user inputs of the digital twin widget"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
# Adapt to reality
|
||||
self.adapt_reality = Button(label_button="Adapt to reality", enabled=True)
|
||||
|
||||
# Energy
|
||||
self.energy = InputNumberField(
|
||||
"energy", "Energy", unit="eV", init=8979, decimals=0, single_step=100, ll=4000, hl=65000
|
||||
)
|
||||
|
||||
# FE Slits Acceptance
|
||||
self.sldi_hacc = InputNumberField(
|
||||
"h_acc",
|
||||
"Horizontal",
|
||||
unit="mrad",
|
||||
prefix="±",
|
||||
init=0.25,
|
||||
decimals=3,
|
||||
single_step=0.01,
|
||||
ll=-0.1,
|
||||
hl=0.9,
|
||||
)
|
||||
self.sldi_vacc = InputNumberField(
|
||||
"v_acc",
|
||||
"Vertical",
|
||||
unit="mrad",
|
||||
prefix="±",
|
||||
init=0.1,
|
||||
decimals=3,
|
||||
single_step=0.01,
|
||||
ll=-0.1,
|
||||
hl=0.5,
|
||||
)
|
||||
self.sldi_ass_group = Group("FE Slits Acceptance", [self.sldi_hacc, self.sldi_vacc])
|
||||
|
||||
# Collimating mirror
|
||||
self.cm_stripe = ComboBox("cm_stripe", "Stripe", ["Si", "Rh", "Pt"])
|
||||
self.cm_pitch = InputNumberField(
|
||||
"cm_pitch",
|
||||
"Pitch",
|
||||
unit="mrad",
|
||||
init=-2.391,
|
||||
decimals=3,
|
||||
single_step=0.01,
|
||||
ll=-4.6,
|
||||
hl=-1.2,
|
||||
)
|
||||
self.cm_pitch_critical = NumberIndicator("Critical Pitch", "mrad", decimals=3)
|
||||
self.cm_refl = NumberIndicator("Reflectivity at x eV", "%", decimals=0)
|
||||
self.cm_refl_harm = NumberIndicator("Reflectivity at x eV", "%", decimals=0)
|
||||
self.cm_ass_group = Group(
|
||||
"Collimating Mirror",
|
||||
[
|
||||
self.cm_stripe,
|
||||
self.cm_pitch,
|
||||
self.cm_pitch_critical,
|
||||
self.cm_refl,
|
||||
self.cm_refl_harm,
|
||||
],
|
||||
)
|
||||
|
||||
# Monochromator
|
||||
self.mo1_mode = ComboBox("mo1_mode", "Mode", ["Monochromatic", "Pinkbeam"])
|
||||
self.mo1_xtal = ComboBox("mo1_xtal", "Crystal", ["Si(111)", "Si(311)"])
|
||||
self.mo1_bragg_angle = NumberIndicator("Bragg Angle", "deg", decimals=1)
|
||||
self.mo1_eres = NumberIndicator("Energy Resolution", "eV", decimals=2)
|
||||
self.mo1_ass_group = Group(
|
||||
"Monochromator", [self.mo1_mode, self.mo1_xtal, self.mo1_bragg_angle, self.mo1_eres]
|
||||
)
|
||||
|
||||
# Focusing Mirror
|
||||
self.fm_stripe = ComboBox(
|
||||
"fm_stripe", "Stripe", ["Rh (toroid)", "Rh (flat)", "Pt (toroid)", "Pt (flat)"]
|
||||
)
|
||||
self.fm_focus = ComboBox("fm_focus", "Focus Type", ["Manual", "Focused", "Defocused"])
|
||||
self.fm_rotx = InputNumberField(
|
||||
"fm_rotx",
|
||||
"Incidence Angle",
|
||||
unit="mrad",
|
||||
init=-2.391,
|
||||
decimals=3,
|
||||
single_step=0.01,
|
||||
ll=-10,
|
||||
hl=2,
|
||||
)
|
||||
self.fm_focx = InputNumberField(
|
||||
"fm_focx",
|
||||
"Beam Size Horizontal",
|
||||
unit="mm",
|
||||
init=1,
|
||||
decimals=1,
|
||||
single_step=0.1,
|
||||
ll=0,
|
||||
hl=30,
|
||||
)
|
||||
self.fm_focy = InputNumberField(
|
||||
"fm_focy",
|
||||
"Beam Size Vertical",
|
||||
unit="mm",
|
||||
init=1,
|
||||
decimals=1,
|
||||
single_step=0.1,
|
||||
ll=0,
|
||||
hl=10,
|
||||
)
|
||||
self.fm_rotx_ideal = NumberIndicator("Incidence Angle for focused beam", "mrad", decimals=3)
|
||||
self.fm_refl = NumberIndicator("Reflectivity at x eV", "%", decimals=0)
|
||||
self.fm_refl_harm = NumberIndicator("Reflectivity at x eV", "%", decimals=0)
|
||||
self.fm_ass_group = Group(
|
||||
"Focusing Mirror",
|
||||
[
|
||||
self.fm_stripe,
|
||||
self.fm_focus,
|
||||
self.fm_rotx,
|
||||
self.fm_focx,
|
||||
self.fm_focy,
|
||||
self.fm_rotx_ideal,
|
||||
self.fm_refl,
|
||||
self.fm_refl_harm,
|
||||
],
|
||||
)
|
||||
|
||||
# Sample
|
||||
self.cm_fm_harm_suppr = NumberIndicator("Total Suppression Factor at x eV", "", decimals=0)
|
||||
self.smpl = InputNumberField(
|
||||
"smpl",
|
||||
"Sample Position",
|
||||
unit="mm",
|
||||
init=23511,
|
||||
decimals=0,
|
||||
single_step=100,
|
||||
ll=23000,
|
||||
hl=30000,
|
||||
)
|
||||
|
||||
# Assemble complete assitant group
|
||||
self.input_group = Group(
|
||||
"User Input",
|
||||
[
|
||||
self.adapt_reality,
|
||||
self.energy,
|
||||
self.sldi_ass_group,
|
||||
self.cm_ass_group,
|
||||
self.mo1_ass_group,
|
||||
self.fm_ass_group,
|
||||
self.cm_fm_harm_suppr,
|
||||
self.smpl,
|
||||
],
|
||||
)
|
||||
|
||||
self._layout.addWidget(self.input_group)
|
||||
self._layout.addStretch()
|
||||
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Panel to move an axis to a certain position
|
||||
"""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.move_widget import (
|
||||
AbsorberWidget,
|
||||
MoveWidget,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import Group
|
||||
|
||||
|
||||
class MoverPanel(QWidget):
|
||||
""" "Panel to move an axis to a certain position"""
|
||||
|
||||
def __init__(self, dev, parent=None):
|
||||
super().__init__(parent)
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
self.mover_widgets = []
|
||||
|
||||
# FE Slits
|
||||
self.sldi_gapx = MoveWidget(
|
||||
dev=dev, motor="sldi_gapx", label="GAPX", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.sldi_gapx)
|
||||
|
||||
self.sldi_gapy = MoveWidget(
|
||||
dev=dev, motor="sldi_gapy", label="GAPY", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.sldi_gapy)
|
||||
|
||||
self.sldi_mov_group = Group("FE Slits", [self.sldi_gapx, self.sldi_gapy])
|
||||
|
||||
# Absorber
|
||||
self.abs = AbsorberWidget(absorber=dev.abs, label="")
|
||||
|
||||
self.abs_group = Group("Absorber", [self.abs])
|
||||
|
||||
# Collimating mirror
|
||||
self.cm_trx = MoveWidget(
|
||||
dev=dev, motor="cm_trx", label="TRX", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.cm_trx)
|
||||
|
||||
self.cm_try = MoveWidget(
|
||||
dev=dev, motor="cm_try", label="TRY", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.cm_try)
|
||||
|
||||
self.cm_bnd = MoveWidget(
|
||||
dev=dev, motor="cm_bnd", label="BENDER", unit="km", decimals=2, deadband=0.2
|
||||
)
|
||||
self.mover_widgets.append(self.cm_bnd)
|
||||
|
||||
self.cm_rotx = MoveWidget(
|
||||
dev=dev, motor="cm_rotx", label="PITCH", unit="mrad", decimals=3, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.cm_rotx)
|
||||
|
||||
self.cm_mov_group = Group(
|
||||
"Collimating Mirror", [self.cm_trx, self.cm_try, self.cm_bnd, self.cm_rotx]
|
||||
)
|
||||
|
||||
# Monochromator
|
||||
self.mo1_bragg_angle = MoveWidget(
|
||||
dev=dev,
|
||||
motor="mo1_bragg_angle",
|
||||
label="Bragg Angle",
|
||||
unit="deg",
|
||||
decimals=3,
|
||||
deadband=0.01,
|
||||
)
|
||||
self.mover_widgets.append(self.mo1_bragg_angle)
|
||||
|
||||
self.mo1_trx = MoveWidget(
|
||||
dev=dev, motor="mo1_trx", label="TRX", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.mo1_trx)
|
||||
|
||||
self.mo1_try = MoveWidget(
|
||||
dev=dev, motor="mo1_try", label="TRY", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.mo1_try)
|
||||
|
||||
self.mo1_mov_group = Group(
|
||||
"Monochromator", [self.mo1_bragg_angle, self.mo1_trx, self.mo1_try]
|
||||
)
|
||||
|
||||
# OP Slits 1
|
||||
self.sl1_centery = MoveWidget(
|
||||
dev=dev, motor="sl1_centery", label="CENTERY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.sl1_centery)
|
||||
|
||||
self.sl1_gapy = MoveWidget(
|
||||
dev=dev, motor="sl1_gapy", label="GAPY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.sl1_gapy)
|
||||
|
||||
self.sl1_mov_group = Group("OP Slits 1", [self.sl1_centery, self.sl1_gapy])
|
||||
|
||||
# OP Beam Monitor 1
|
||||
self.bm1_try = MoveWidget(
|
||||
dev=dev, motor="bm1_try", label="TRY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.bm1_try)
|
||||
|
||||
self.bm1_mov_group = Group("OP Beam Monitor 1", [self.bm1_try])
|
||||
|
||||
# Focusing Mirror
|
||||
self.fm_trx = MoveWidget(
|
||||
dev=dev, motor="fm_trx", label="TRX", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.fm_trx)
|
||||
|
||||
self.fm_try = MoveWidget(
|
||||
dev=dev, motor="fm_try", label="TRY", unit="mm", decimals=2, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.fm_try)
|
||||
|
||||
self.fm_bnd = MoveWidget(
|
||||
dev=dev, motor="fm_bnd", label="BENDER", unit="km", decimals=2, deadband=0.2
|
||||
)
|
||||
self.mover_widgets.append(self.fm_bnd)
|
||||
|
||||
self.fm_rotx = MoveWidget(
|
||||
dev=dev, motor="fm_rotx", label="PITCH", unit="mrad", decimals=3, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.fm_rotx)
|
||||
|
||||
self.fm_roty = MoveWidget(
|
||||
dev=dev, motor="fm_roty", label="YAW", unit="mrad", decimals=3, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.fm_roty)
|
||||
|
||||
self.fm_rotz = MoveWidget(
|
||||
dev=dev, motor="fm_rotz", label="ROLL", unit="mrad", decimals=3, deadband=0.01
|
||||
)
|
||||
self.mover_widgets.append(self.fm_rotz)
|
||||
|
||||
self.fm_mov_group = Group(
|
||||
"Focusing Mirror",
|
||||
[self.fm_trx, self.fm_try, self.fm_bnd, self.fm_rotx, self.fm_roty, self.fm_rotz],
|
||||
)
|
||||
|
||||
# OP Slits 2
|
||||
self.sl2_centery = MoveWidget(
|
||||
dev=dev, motor="sl2_centery", label="CENTERY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.sl2_centery)
|
||||
|
||||
self.sl2_gapy = MoveWidget(
|
||||
dev=dev, motor="sl2_gapy", label="GAPY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.sl2_gapy)
|
||||
|
||||
self.sl2_mov_group = Group("OP Slits 2", [self.sl2_centery, self.sl2_gapy])
|
||||
|
||||
# OP Beam Monitor 2
|
||||
self.bm2_try = MoveWidget(
|
||||
dev=dev, motor="bm2_try", label="TRY", unit="mm", decimals=2, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.bm2_try)
|
||||
|
||||
self.bm2_mov_group = Group("OP Beam Monitor 2", [self.bm2_try])
|
||||
|
||||
# Optical Table
|
||||
self.ot_try = MoveWidget(
|
||||
dev=dev, motor="ot_try", label="TRY", unit="mm", decimals=2, deadband=0.2
|
||||
)
|
||||
self.mover_widgets.append(self.ot_try)
|
||||
|
||||
self.ot_rotx = MoveWidget(
|
||||
dev=dev, motor="ot_rotx", label="ROTX", unit="mrad", decimals=3, deadband=0.05
|
||||
)
|
||||
self.mover_widgets.append(self.ot_rotx)
|
||||
|
||||
self.ot_mov_group = Group("Optical Table", [self.ot_try, self.ot_rotx])
|
||||
|
||||
# Experimental Station 0
|
||||
self.es0wi_try = MoveWidget(
|
||||
dev=dev, motor="es0wi_try", label="ES0 WI", unit="mm", decimals=0, deadband=0.1
|
||||
)
|
||||
self.mover_widgets.append(self.es0wi_try)
|
||||
|
||||
self.es0_mov_group = Group("Expperimental Station 0", [self.es0wi_try])
|
||||
|
||||
# Experimental Station 1
|
||||
self.ot_es1_trz = MoveWidget(
|
||||
dev=dev, motor="ot_es1_trz", label="ES1 TRZ", unit="mm", decimals=0, deadband=5
|
||||
)
|
||||
self.mover_widgets.append(self.ot_es1_trz)
|
||||
|
||||
self.es1_mov_group = Group("Expperimental Station 1", [self.ot_es1_trz])
|
||||
|
||||
# Assemble complete mover group
|
||||
self.mover_group = Group(
|
||||
"Mover",
|
||||
[
|
||||
self.sldi_mov_group,
|
||||
self.abs_group,
|
||||
self.cm_mov_group,
|
||||
self.mo1_mov_group,
|
||||
self.sl1_mov_group,
|
||||
self.bm1_mov_group,
|
||||
self.fm_mov_group,
|
||||
self.sl2_mov_group,
|
||||
self.bm2_mov_group,
|
||||
self.ot_mov_group,
|
||||
self.es0_mov_group,
|
||||
self.es1_mov_group,
|
||||
],
|
||||
)
|
||||
|
||||
self._layout.addWidget(self.mover_group)
|
||||
self._layout.addStretch()
|
||||
|
||||
def apply_theme(self, theme: Literal["dark", "light"]):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (str): Theme, either "dark" or "light"
|
||||
"""
|
||||
for widget in self.mover_widgets:
|
||||
widget.apply_theme(theme)
|
||||
@@ -0,0 +1,330 @@
|
||||
"""
|
||||
Two plot classes to plot side-view and surface-view
|
||||
"""
|
||||
|
||||
from typing import Literal, Optional, cast
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from bec_lib import bec_logger
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtGui import QBrush, QColor
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QApplication, QGraphicsRectItem, QHBoxLayout, QVBoxLayout, QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calculations.calc_varia import (
|
||||
mirror_surface_geometries,
|
||||
mo_surface_geometries,
|
||||
pipe_geometries,
|
||||
wall_geometries,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.types import DataDict, SurfaceDict
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import Group
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class SurfacePlots(QWidget):
|
||||
"""Plot widget with two curves and legend."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._layout = QHBoxLayout(self)
|
||||
|
||||
self.surfaces: dict[str, SurfaceDict] = {
|
||||
"assistant": {
|
||||
"cm": {"x": [], "y": []},
|
||||
"mo1_1": {"x": [], "y": []},
|
||||
"mo1_2": {"x": [], "y": []},
|
||||
"fm": {"x": [], "y": []},
|
||||
},
|
||||
"reality": {
|
||||
"cm": {"x": [], "y": []},
|
||||
"mo1_1": {"x": [], "y": []},
|
||||
"mo1_2": {"x": [], "y": []},
|
||||
"fm": {"x": [], "y": []},
|
||||
},
|
||||
}
|
||||
|
||||
self.plots = {"fm": {}, "mo1_2": {}, "mo1_1": {}, "cm": {}}
|
||||
|
||||
self.color_impenetrable = (0, 0, 0)
|
||||
self.colors = [(255, 255, 0), (255, 0, 255)]
|
||||
self.text_color = (255, 255, 255)
|
||||
|
||||
# Create plot widgets
|
||||
for name, widget in self.plots.items():
|
||||
plot_widget = pg.PlotWidget()
|
||||
plot_widget.getAxis("bottom").enableAutoSIPrefix(False)
|
||||
|
||||
plot_group = Group("Surface " + name, [plot_widget])
|
||||
|
||||
plot_widget.setLabel("left", "Z [mm]")
|
||||
plot_widget.setLabel("bottom", "X [mm]")
|
||||
plot_widget.setMouseEnabled(x=False, y=False)
|
||||
plot_widget.setMenuEnabled(False)
|
||||
plot_widget.hideButtons()
|
||||
|
||||
widget["widget"] = plot_widget
|
||||
self._layout.addWidget(plot_group)
|
||||
|
||||
# Create surfaces
|
||||
for idx, scene in enumerate(self.surfaces):
|
||||
for name, _ in self.surfaces[scene].items():
|
||||
if scene in "assistant":
|
||||
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
|
||||
pen = pg.mkPen(
|
||||
QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine
|
||||
)
|
||||
z_value = 2
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1)
|
||||
z_value = 1
|
||||
widget = self.plots[name]
|
||||
self.plots[name][scene] = widget["widget"].plot(
|
||||
[], [], pen=pen, name=scene, brush=brush, fillLevel=0
|
||||
)
|
||||
self.plots[name][scene].setZValue(z_value)
|
||||
|
||||
self.walls = []
|
||||
self.texts = []
|
||||
|
||||
self.plot_walls()
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None.
|
||||
"""
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
bg_color = pg.getConfigOption("background")
|
||||
fg_color = pg.getConfigOption("foreground")
|
||||
for _, plot in self.plots.items():
|
||||
# Background
|
||||
plot["widget"].setBackground(bg_color)
|
||||
# Axes (tick marks, tick labels, axis line)
|
||||
for axis in ["left", "bottom", "right", "top"]:
|
||||
ax = plot["widget"].getAxis(axis)
|
||||
ax.setPen(pg.mkPen(color=fg_color))
|
||||
ax.setTextPen(pg.mkPen(color=fg_color))
|
||||
|
||||
if theme == "light":
|
||||
self.color_impenetrable = (30, 30, 30)
|
||||
self.colors = [(79, 163, 224), (240, 128, 60)]
|
||||
self.text_color = (255, 255, 255)
|
||||
else: # dark theme
|
||||
self.color_impenetrable = (180, 180, 180)
|
||||
self.colors = [(26, 111, 173), (212, 83, 10)]
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
for idx, scene in enumerate(self.surfaces):
|
||||
for name, _ in self.surfaces[scene].items():
|
||||
if scene in "assistant":
|
||||
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
|
||||
pen = pg.mkPen(
|
||||
QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine
|
||||
)
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=0)
|
||||
self.plots[name][scene].setPen(pen)
|
||||
self.plots[name][scene].setBrush(brush)
|
||||
|
||||
for wall in self.walls:
|
||||
wall.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
|
||||
wall.setBrush(QBrush(QColor(*self.color_impenetrable)))
|
||||
|
||||
for text in self.texts:
|
||||
text.setColor(self.text_color)
|
||||
|
||||
def plot_walls(self):
|
||||
"""Plot walls"""
|
||||
|
||||
def plot_surface(widget, surfaces):
|
||||
for name, surface in surfaces.items():
|
||||
rect = QGraphicsRectItem(*surface)
|
||||
rect.setBrush(QBrush(QColor(*self.color_impenetrable)))
|
||||
rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
|
||||
widget.addItem(rect)
|
||||
text = pg.TextItem(name, color=self.text_color, anchor=(0.5, 0.5))
|
||||
widget.addItem(text)
|
||||
text.setPos(surface[0] + surface[2] / 2, surface[1] + surface[3] / 2)
|
||||
text.setZValue(10)
|
||||
self.walls.append(rect)
|
||||
self.texts.append(text)
|
||||
|
||||
for name, plot in self.plots.items():
|
||||
if name in "cm":
|
||||
plot_surface(plot["widget"], mirror_surface_geometries("cm"))
|
||||
elif name in "mo1_1":
|
||||
plot_surface(plot["widget"], mo_surface_geometries("mo1", 0))
|
||||
elif name in "mo1_2":
|
||||
plot_surface(plot["widget"], mo_surface_geometries("mo1", 1))
|
||||
elif name in "fm":
|
||||
plot_surface(plot["widget"], mirror_surface_geometries("fm_flat"))
|
||||
plot_surface(plot["widget"], mirror_surface_geometries("fm_toroid"))
|
||||
else:
|
||||
raise ValueError(f"Plot {name} not found!")
|
||||
for name, plot in self.plots.items():
|
||||
plot["widget"].disableAutoRange()
|
||||
|
||||
def update_surfaces(self, scene: Literal["assistant", "reality"], data: SurfaceDict):
|
||||
"""Update the curves of the plot
|
||||
|
||||
Args:
|
||||
scene (str): The scene to update, either "assistant" or "reality".
|
||||
data (DataDict): The new data to plot, with keys "x" and "y",
|
||||
each containing a list of values.
|
||||
"""
|
||||
self.surfaces[scene] = data
|
||||
for name, device in self.surfaces[scene].items():
|
||||
device = cast(DataDict, device)
|
||||
plot = self.plots[name][scene]
|
||||
x = np.array(device["x"] + [device["x"][0]]) if len(device["x"]) != 0 else np.array([])
|
||||
y = np.array(device["y"] + [device["y"][0]]) if len(device["y"]) != 0 else np.array([])
|
||||
plot.setData(x=x, y=y)
|
||||
|
||||
|
||||
class SideviewPlot(QWidget):
|
||||
"""Plot widget with two curves and legend."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._layout = QVBoxLayout(self)
|
||||
# self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
self.plot_widget = pg.PlotWidget()
|
||||
self.plot_widget.getAxis("bottom").enableAutoSIPrefix(False)
|
||||
self.plot_widget.invertX(True)
|
||||
self.plot_widget.addLegend()
|
||||
|
||||
self.color_impenetrable = (0, 0, 0)
|
||||
self.colors = [(255, 255, 0), (255, 0, 255)]
|
||||
|
||||
self.data: dict[str, DataDict] = {
|
||||
"assistant": {"x": [0, 1000, 2000], "y": [0, 20, 30]},
|
||||
"reality": {"x": [0, 1000, 2000], "y": [0, 15, 50]},
|
||||
}
|
||||
|
||||
self.plots = {}
|
||||
|
||||
self.pipes = []
|
||||
self.walls = []
|
||||
|
||||
for idx, scene in enumerate(self.data.keys()):
|
||||
if scene in "assistant":
|
||||
pen = pg.mkPen(color=self.colors[idx], width=2, style=Qt.PenStyle.DotLine)
|
||||
z_value = 2
|
||||
else:
|
||||
pen = pg.mkPen(color=self.colors[idx], width=2)
|
||||
z_value = 1
|
||||
self.plots[scene] = self.plot_widget.plot([], [], pen=pen, name=scene)
|
||||
self.plots[scene].setZValue(z_value)
|
||||
|
||||
self.plot_group = Group("Side View", [self.plot_widget])
|
||||
|
||||
self.plot_widget.setLabel("left", "Height [mm]")
|
||||
self.plot_widget.setLabel("bottom", "Distance [mm]")
|
||||
self.plot_widget.setMouseEnabled(x=False, y=False)
|
||||
self.plot_widget.setXRange(0, 25000, 0.1) # pylint: disable=E1121 # type: ignore
|
||||
self.plot_widget.setYRange(-20, 120, 0.1) # pylint: disable=E1121 # type: ignore
|
||||
self.plot_widget.setMenuEnabled(False)
|
||||
self.plot_widget.hideButtons()
|
||||
|
||||
self._layout.addWidget(self.plot_group)
|
||||
self._layout.addStretch()
|
||||
|
||||
self.plot_vacuum_pipes()
|
||||
self.plot_walls()
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None.
|
||||
"""
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
bg_color = pg.getConfigOption("background")
|
||||
fg_color = pg.getConfigOption("foreground")
|
||||
# Background
|
||||
self.plot_widget.setBackground(bg_color)
|
||||
# Axes (tick marks, tick labels, axis line)
|
||||
for axis in ["left", "bottom", "right", "top"]:
|
||||
ax = self.plot_widget.getAxis(axis)
|
||||
ax.setPen(pg.mkPen(color=fg_color))
|
||||
ax.setTextPen(pg.mkPen(color=fg_color))
|
||||
|
||||
if theme == "light":
|
||||
self.color_impenetrable = (30, 30, 30)
|
||||
self.colors = [(79, 163, 224), (240, 128, 60)]
|
||||
self.text_color = (255, 255, 255)
|
||||
else: # dark theme
|
||||
self.color_impenetrable = (180, 180, 180)
|
||||
self.colors = [(26, 111, 173), (212, 83, 10)]
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
for idx, scene in enumerate(self.data):
|
||||
if scene in "assistant":
|
||||
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.PenStyle.DashLine)
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3)
|
||||
self.plots[scene].setPen(pen)
|
||||
self.plots[scene].setBrush(brush)
|
||||
|
||||
for wall in self.walls:
|
||||
wall.setPen(pg.mkPen(color=self.color_impenetrable, width=3))
|
||||
wall.setBrush(QBrush(QColor(*self.color_impenetrable)))
|
||||
|
||||
for pipe in self.pipes:
|
||||
pipe.setPen(pg.mkPen(color=self.color_impenetrable, width=3))
|
||||
|
||||
def plot_vacuum_pipes(self):
|
||||
"""Plot vacuum pipes"""
|
||||
pipes = pipe_geometries()
|
||||
for pipe in pipes:
|
||||
self.pipes.append(
|
||||
self.plot_widget.plot(
|
||||
x=pipe["x"], y=pipe["y"], pen=pg.mkPen(color=self.color_impenetrable, width=2)
|
||||
)
|
||||
)
|
||||
|
||||
def plot_walls(self):
|
||||
"""Plot walls"""
|
||||
walls = wall_geometries()
|
||||
for wall in walls:
|
||||
rect = QGraphicsRectItem(wall[0], wall[1], wall[2], wall[3])
|
||||
rect.setBrush(QBrush(QColor(*self.color_impenetrable)))
|
||||
rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
|
||||
self.plot_widget.addItem(rect)
|
||||
self.walls.append(rect)
|
||||
|
||||
def update_curves(self, scene: Literal["assistant", "reality"], data: DataDict):
|
||||
"""Update the curves of the plot
|
||||
|
||||
Args:
|
||||
scene (str): The scene to update, either "assistant" or "reality".
|
||||
data (DataDict): The new data to plot, with keys "x" and "y",
|
||||
each containing a list of values.
|
||||
"""
|
||||
self.data[scene] = data
|
||||
plot = self.plots[scene]
|
||||
plot.setData(x=self.data[scene]["x"], y=self.data[scene]["y"])
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Settings panel for the digital twin widget
|
||||
"""
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
|
||||
Button,
|
||||
Group,
|
||||
TextIndicator,
|
||||
)
|
||||
|
||||
|
||||
class SettingsPanel(QWidget):
|
||||
"""Settings panel for the digital twin widget"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
# Reload offsets
|
||||
self.load_offsets = Button(label="Load Offsets", label_button="Load", enabled=True)
|
||||
self.offsets_status = TextIndicator(label="Offsets")
|
||||
self.show_offsets = Button(label="Show Offsets", label_button="Show", enabled=True)
|
||||
|
||||
# Assemble complete offset group
|
||||
self.offset_group = Group(
|
||||
"Axes Offsets", [self.load_offsets, self.offsets_status, self.show_offsets]
|
||||
)
|
||||
|
||||
self._layout.addWidget(self.offset_group)
|
||||
self._layout.addStretch()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Types used for the beamline config and for plotting data"""
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class ConfigDict(TypedDict):
|
||||
"""
|
||||
Typed dictionary representing the beamline configuration.
|
||||
|
||||
Attributes:
|
||||
energy (float): Beam energy.
|
||||
h_acc (float): Horizontal acceptance.
|
||||
v_acc (float): Vertical acceptance.
|
||||
cm_pitch (float): CM pitch angle.
|
||||
cm_stripe (str): CM stripe name.
|
||||
cm_trx (float): CM translation x.
|
||||
mo1_mode (str): MO1 mode.
|
||||
mo1_xtal (str): MO1 crystal.
|
||||
mo1_bragg (float): MO1 Bragg angle.
|
||||
fm_rotx (float): FM rotation x.
|
||||
fm_stripe (str): FM stripe name.
|
||||
fm_trx (float): FM translation x.
|
||||
fm_qy (float): FM qy value.
|
||||
fm_gain_height (int): FM gain height.
|
||||
smpl (float): Sample value.
|
||||
"""
|
||||
|
||||
energy: float
|
||||
h_acc: float
|
||||
v_acc: float
|
||||
cm_pitch: float
|
||||
cm_stripe: str
|
||||
cm_trx: float
|
||||
mo1_mode: str
|
||||
mo1_xtal: str
|
||||
mo1_bragg: float
|
||||
fm_rotx: float
|
||||
fm_stripe: str
|
||||
fm_trx: float
|
||||
fm_qy: None | float
|
||||
fm_gain_height: int
|
||||
smpl: float
|
||||
|
||||
|
||||
class DataDict(TypedDict):
|
||||
"""
|
||||
Typed dictionary representing plot data.
|
||||
|
||||
Attributes:
|
||||
x (list[float]): List of x-axis values.
|
||||
y (list[float]): List of y-axis values.
|
||||
"""
|
||||
|
||||
x: list
|
||||
y: list
|
||||
|
||||
|
||||
class SurfaceDict(TypedDict):
|
||||
"""
|
||||
Typed dictionary representing the surfaces of a scene,
|
||||
grouping plot data by surface type.
|
||||
|
||||
Attributes:
|
||||
cm (DataDict): Data for the cm surface.
|
||||
mo1_1 (DataDict): Data for the mo1_1 surface.
|
||||
mo1_2 (DataDict): Data for the mo1_2 surface.
|
||||
fm (DataDict): Data for the fm surface.
|
||||
"""
|
||||
|
||||
cm: DataDict
|
||||
mo1_1: DataDict
|
||||
mo1_2: DataDict
|
||||
fm: DataDict
|
||||
+246
-163
@@ -1,28 +1,33 @@
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
"""Move widget to display an axis and also move it through BEC"""
|
||||
|
||||
# import qtawesome as qta
|
||||
import threading
|
||||
import time
|
||||
from typing import Literal, Optional
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from bec_qthemes import material_icon
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_lib import bec_logger
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtCore import Property # type: ignore[attr-defined]
|
||||
from qtpy.QtCore import Signal # type: ignore[attr-defined]
|
||||
from qtpy.QtCore import QObject, QPropertyAnimation, Qt, QThread
|
||||
from qtpy.QtGui import QTransform
|
||||
from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QPushButton, QWidget
|
||||
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
from qtpy.QtCore import Qt, QThread, Signal, QObject, Property, QPropertyAnimation
|
||||
from qtpy.QtWidgets import (
|
||||
QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
||||
QDoubleSpinBox, QFrame, QWidget, QApplication
|
||||
)
|
||||
from qtpy.QtGui import QTransform
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class Status:
|
||||
IN_POSITION = "in_position" # green mdi.check-circle
|
||||
"""Status class for the axis"""
|
||||
|
||||
IN_POSITION = "in_position" # green mdi.check-circle
|
||||
NOT_IN_POSITION = "not_in_position" # orange mdi.close-circle
|
||||
MOVING = "moving" # blue mdi.loading (spinning)
|
||||
ERROR = "error" # red mdi.alert-circle
|
||||
MOVING = "moving" # blue mdi.loading (spinning)
|
||||
ERROR = "error" # red mdi.alert-circle
|
||||
|
||||
|
||||
class StatusIcon(QWidget):
|
||||
"""
|
||||
@@ -33,10 +38,10 @@ class StatusIcon(QWidget):
|
||||
ICON_SIZE = 20
|
||||
|
||||
_ICON_MAP = {
|
||||
Status.IN_POSITION: ("check_circle", "#27ae60"),
|
||||
Status.IN_POSITION: ("check_circle", "#27ae60"),
|
||||
Status.NOT_IN_POSITION: ("cancel", "#e6d922"),
|
||||
Status.ERROR: ("warning", "#e74c3c"),
|
||||
Status.MOVING: ("cycle", "#2980b9"),
|
||||
Status.ERROR: ("warning", "#e74c3c"),
|
||||
Status.MOVING: ("cycle", "#2980b9"),
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -46,10 +51,10 @@ class StatusIcon(QWidget):
|
||||
|
||||
self._label = QLabel(self)
|
||||
self._label.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
self._label.setAlignment(Qt.AlignCenter)
|
||||
self._label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
|
||||
self._spin_anim = QPropertyAnimation(self, b"rotation")
|
||||
self._spin_anim = QPropertyAnimation(self, b"rotation") # type: ignore[call-arg]
|
||||
self._spin_anim.setStartValue(0)
|
||||
self._spin_anim.setEndValue(360)
|
||||
self._spin_anim.setDuration(1000)
|
||||
@@ -57,26 +62,56 @@ class StatusIcon(QWidget):
|
||||
|
||||
self.set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def get_rotation(self):
|
||||
def get_rotation(self) -> float:
|
||||
"""
|
||||
Return the current rotation angle in degrees.
|
||||
|
||||
Returns:
|
||||
float: Rotation angle in deg
|
||||
"""
|
||||
return self._rotation
|
||||
|
||||
def set_rotation(self, angle):
|
||||
def set_rotation(self, angle: float):
|
||||
"""
|
||||
Set the rotation angle and update the displayed pixmap.
|
||||
|
||||
Rotates the current base pixmap around its center point using a smooth
|
||||
transformation. Has no effect on the display if no base pixmap is set.
|
||||
|
||||
Args:
|
||||
angle (float): Rotation angle in degrees, clockwise.
|
||||
"""
|
||||
self._rotation = angle
|
||||
if self._current_pixmap_base is not None:
|
||||
cx = self._current_pixmap_base.width() / 2
|
||||
cy = self._current_pixmap_base.height() / 2
|
||||
t = QTransform().translate(cx, cy).rotate(angle).translate(-cx, -cy)
|
||||
self._label.setPixmap(self._current_pixmap_base.transformed(t, Qt.SmoothTransformation))
|
||||
self._label.setPixmap(
|
||||
self._current_pixmap_base.transformed(t, Qt.TransformationMode.SmoothTransformation)
|
||||
)
|
||||
|
||||
rotation = Property(float, get_rotation, set_rotation)
|
||||
rotation = Property(float, get_rotation, set_rotation) # type: ignore[call-arg]
|
||||
|
||||
def set_status(self, status: str):
|
||||
"""
|
||||
Update the widget's status and refresh the displayed icon accordingly.
|
||||
|
||||
Looks up the icon name and color associated with the given status from
|
||||
``_ICON_MAP``, renders a new pixmap, and starts or stops the spin
|
||||
animation depending on whether the status is ``Status.MOVING``. Returns
|
||||
early without any updates if the status has not changed.
|
||||
|
||||
Args:
|
||||
status (str): The new status value. Must be a key in ``_ICON_MAP``.
|
||||
"""
|
||||
if status == self._status:
|
||||
return
|
||||
self._status = status
|
||||
|
||||
icon_name, color = self._ICON_MAP[status]
|
||||
icon = material_icon(icon_name, size=(self.ICON_SIZE, self.ICON_SIZE), color=color, convert_to_pixmap=True)
|
||||
icon = material_icon(
|
||||
icon_name, size=(self.ICON_SIZE, self.ICON_SIZE), color=color, convert_to_pixmap=True
|
||||
)
|
||||
self._current_pixmap_base = icon
|
||||
|
||||
if status == Status.MOVING:
|
||||
@@ -85,14 +120,16 @@ class StatusIcon(QWidget):
|
||||
self._spin_anim.stop()
|
||||
self._label.setPixmap(icon)
|
||||
|
||||
|
||||
class MotionWorker(QObject):
|
||||
"""
|
||||
Executes motion on the specified motor and includes some safety during
|
||||
motion for certain motors.
|
||||
"""
|
||||
|
||||
position_changed = Signal(float)
|
||||
error = Signal(bool) # True = error
|
||||
finished = Signal(bool) # True = reached target, False = stopped
|
||||
error = Signal(bool) # True = error
|
||||
finished = Signal(bool) # True = reached target, False = stopped
|
||||
|
||||
def __init__(self, dev, motor, target_pos: float):
|
||||
super().__init__()
|
||||
@@ -102,103 +139,108 @@ class MotionWorker(QObject):
|
||||
self._stop_flag = threading.Event()
|
||||
|
||||
def stop(self):
|
||||
"""Sets the stop flag"""
|
||||
self._stop_flag.set()
|
||||
|
||||
# def run(self):
|
||||
# logger.info(f'Would run motor {self.motor}')
|
||||
# simulated_run_time = 3
|
||||
# start = time.time()
|
||||
# while (time.time() - start) < simulated_run_time:
|
||||
# if self._stop_flag.is_set():
|
||||
# break
|
||||
# time.sleep(0.01)
|
||||
|
||||
# # self.motor.move(self._target, relative=False)
|
||||
# # while self.motor.motor_is_moving.get():
|
||||
# # if self._stop_flag.is_set():
|
||||
# # self.motor.motor_stop()
|
||||
# # self.position_changed.emit(self.motor.read[self.name]['value'])
|
||||
# # time.sleep(0.1)
|
||||
# self.finished.emit(True)
|
||||
|
||||
def run(self):
|
||||
"""Prepares the movement based on the axis (motor)"""
|
||||
match self.motor:
|
||||
case 'sldi_gapx' | 'sldi_gapy' | 'sldi_centerx' | 'sldi_centery':
|
||||
case "sldi_gapx" | "sldi_gapy" | "sldi_centerx" | "sldi_centery":
|
||||
self.motion()
|
||||
case 'cm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_bnd':
|
||||
p1 = (1/(self.dev.cm_bnd_radius.read()['cm_bnd_radius']['value']*1e3) + 0.0284)/2e-6
|
||||
p2 = (1/(self._target*1e3) + 0.0284)/2e-6
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['cm_bnd_radius']}
|
||||
case "cm_trx":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[{"device": self.dev["cm_roty"], "abs_tol": 0.05}],
|
||||
)
|
||||
case 'mo1_try' | 'mo1_trx' | 'mo1_roty':
|
||||
case "cm_roty":
|
||||
self.motion(
|
||||
abs_closed=True, surveyed_axes=[{"device": self.dev["cm_trx"], "abs_tol": 0.05}]
|
||||
)
|
||||
case "cm_try":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["cm_rotx"], "abs_tol": 0.05},
|
||||
{"device": self.dev["cm_rotz"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "cm_rotx":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["cm_try"], "abs_tol": 0.05},
|
||||
{"device": self.dev["cm_rotz"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "cm_rotz":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["cm_try"], "abs_tol": 0.05},
|
||||
{"device": self.dev["cm_rotx"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "cm_bnd":
|
||||
p1 = (
|
||||
1 / (self.dev.cm_bnd_radius.read()["cm_bnd_radius"]["value"] * 1e3) + 0.0284
|
||||
) / 2e-6
|
||||
p2 = (1 / (self._target * 1e3) + 0.0284) / 2e-6
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb={"device": self.dev["cm_bnd_radius"]})
|
||||
case "mo1_try" | "mo1_trx" | "mo1_roty":
|
||||
self.motion(abs_closed=True)
|
||||
case 'mo1_bragg_angle':
|
||||
case "mo1_bragg_angle":
|
||||
self.motion()
|
||||
case 'sl1_centery' | 'sl1_gapy' | 'bm1_try':
|
||||
case "sl1_centery" | "sl1_gapy" | "bm1_try":
|
||||
self.motion()
|
||||
case 'fm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_bnd':
|
||||
p1 = (1/(self.dev.fm_bnd_radius.read()['fm_bnd_radius']['value']*1e3) + 4.28e-5)/1.84e-9
|
||||
p2 = (1/(self._target*1e3) + 4.28e-5)/1.84e-9
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['fm_bnd_radius']}
|
||||
case "fm_trx":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[{"device": self.dev["fm_roty"], "abs_tol": 0.05}],
|
||||
)
|
||||
case 'sl2_centery' | 'sl2_gapy' | 'bm2_try':
|
||||
case "fm_roty":
|
||||
self.motion(
|
||||
abs_closed=True, surveyed_axes=[{"device": self.dev["fm_trx"], "abs_tol": 0.05}]
|
||||
)
|
||||
case "fm_try":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["fm_rotx"], "abs_tol": 0.05},
|
||||
{"device": self.dev["fm_rotz"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "fm_rotx":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["fm_try"], "abs_tol": 0.05},
|
||||
{"device": self.dev["fm_rotz"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "fm_rotz":
|
||||
self.motion(
|
||||
abs_closed=True,
|
||||
surveyed_axes=[
|
||||
{"device": self.dev["fm_try"], "abs_tol": 0.05},
|
||||
{"device": self.dev["fm_rotx"], "abs_tol": 0.05},
|
||||
],
|
||||
)
|
||||
case "fm_bnd":
|
||||
p1 = (
|
||||
1 / (self.dev.fm_bnd_radius.read()["fm_bnd_radius"]["value"] * 1e3) + 4.28e-5
|
||||
) / 1.84e-9
|
||||
p2 = (1 / (self._target * 1e3) + 4.28e-5) / 1.84e-9
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb={"device": self.dev["fm_bnd_radius"]})
|
||||
case "sl2_centery" | "sl2_gapy" | "bm2_try":
|
||||
self.motion()
|
||||
case 'ot_try' | 'ot_rotx' | 'ot_es1_trz':
|
||||
case "ot_try" | "ot_rotx" | "ot_es1_trz":
|
||||
self.motion()
|
||||
case _:
|
||||
logger.warning(f'Motor {self.motor} not integrated in digital twin!')
|
||||
logger.warning(f"Motor {self.motor} not integrated in digital twin!")
|
||||
|
||||
def motion(self, abs_closed=False, relative=False, rb=None, surveyed_axes = None):
|
||||
def motion(self, abs_closed: bool = False, relative: bool = False, rb=None, surveyed_axes=None):
|
||||
"""
|
||||
Moves an axis while surverying a set of axes (if set).
|
||||
Example surveyed_axes:
|
||||
@@ -210,34 +252,42 @@ class MotionWorker(QObject):
|
||||
if abs_closed:
|
||||
if self.dev.abs.status.get() == ABS_STATUS.OPEN:
|
||||
status = self.dev.abs.close()
|
||||
# TODO Set timeout to 0.001 and check if it actually raises (it should not start motion).
|
||||
# TODO Set timeout to 0.001 and check if it actually raises
|
||||
# (it should not start motion).
|
||||
# Check of behavior of digital twin afterwards.
|
||||
status.wait(timeout=5)
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
surv_ax['name'] = surv_ax['device'].dotted_name
|
||||
surv_ax['old_value'] = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
||||
surv_ax["name"] = surv_ax["device"].dotted_name
|
||||
surv_ax["old_value"] = surv_ax["device"].read(cached=True)[surv_ax["name"]]["value"]
|
||||
if rb is not None:
|
||||
rb['name'] = rb['device'].dotted_name
|
||||
self.dev[self.motor].move(self._target, relative=relative)
|
||||
time.sleep(0.5)
|
||||
while self.dev[self.motor].motor_is_moving.get():
|
||||
rb["name"] = rb["device"].dotted_name
|
||||
status = self.dev[self.motor].move(self._target, relative=relative)
|
||||
last_check = time.time()
|
||||
update_interval = 0.1
|
||||
while status.status == "RUNNING":
|
||||
now = time.time()
|
||||
if time.time() - last_check < update_interval:
|
||||
time.sleep(0.01)
|
||||
last_check = now
|
||||
if self._stop_flag.is_set():
|
||||
self.dev[self.motor].stop()
|
||||
self._stop_flag.clear()
|
||||
if rb is not None:
|
||||
self.position_changed.emit(rb['device'].read(cached=True)[rb['name']]['value'])
|
||||
self.position_changed.emit(rb["device"].read(cached=True)[rb["name"]]["value"])
|
||||
else:
|
||||
self.position_changed.emit(self.dev[self.motor].read(cached=True)[self.motor]['value'])
|
||||
self.position_changed.emit(
|
||||
self.dev[self.motor].read(cached=True)[self.motor]["value"]
|
||||
)
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
fb = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
||||
if abs(fb - surv_ax['old_value']) > surv_ax['abs_tol']:
|
||||
fb = surv_ax["device"].read(cached=True)[surv_ax["name"]]["value"]
|
||||
if abs(fb - surv_ax["old_value"]) > surv_ax["abs_tol"]:
|
||||
self.dev[self.motor].stop()
|
||||
self.error.emit(1)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
class MoveWidget(QWidget):
|
||||
"""
|
||||
@@ -248,7 +298,7 @@ class MoveWidget(QWidget):
|
||||
- Start / Stop button
|
||||
"""
|
||||
|
||||
def __init__(self, dev, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
||||
def __init__(self, dev, motor, label: str = "", unit=None, decimals=3, deadband=0.0):
|
||||
super().__init__()
|
||||
self.fb = 0.0
|
||||
self.target = 0
|
||||
@@ -276,12 +326,12 @@ class MoveWidget(QWidget):
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Target
|
||||
self.target_label = QLabel('-')
|
||||
self.target_label = QLabel("-")
|
||||
self.target_label.setFixedWidth(100)
|
||||
layout.addWidget(self.target_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label = QLabel("-")
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
@@ -297,66 +347,80 @@ class MoveWidget(QWidget):
|
||||
self.btn_action.setFixedHeight(20)
|
||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
||||
layout.addWidget(self.btn_action)
|
||||
self.btn_mode = 'start'
|
||||
self.btn_mode = "start"
|
||||
|
||||
self._apply_button_style("start")
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def apply_theme(self, theme=None):
|
||||
def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None.
|
||||
"""
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
if theme == "light":
|
||||
self.text_color = {'target': (79, 163, 224), 'fb': (240, 128, 60)}
|
||||
else: # dark theme
|
||||
self.text_color = {'target': (26, 111, 173), 'fb': (212, 83, 10)}
|
||||
r, g, b = self.text_color['target']
|
||||
self.target_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
r, g, b = self.text_color['fb']
|
||||
self.fb_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
self.text_color = {"target": (79, 163, 224), "fb": (240, 128, 60)}
|
||||
else: # dark theme
|
||||
self.text_color = {"target": (26, 111, 173), "fb": (212, 83, 10)}
|
||||
r, g, b = self.text_color["target"]
|
||||
self.target_label.setStyleSheet(f"QLabel {{color: rgb({r}, {g}, {b})}}")
|
||||
r, g, b = self.text_color["fb"]
|
||||
self.fb_label.setStyleSheet(f"QLabel {{color: rgb({r}, {g}, {b})}}")
|
||||
|
||||
if self.btn_mode == 'start':
|
||||
if self.btn_mode == "start":
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
"QPushButton "
|
||||
+ f"{{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else:
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
"QPushButton "
|
||||
+ f"{{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
def set_target(self, target):
|
||||
"""Change the target value in the ui"""
|
||||
self.target = target
|
||||
text = f'{target:.{int(self.decimals)}f}'
|
||||
text = f"{target:.{int(self.decimals)}f}"
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
text = text + " " + self.unit
|
||||
self.target_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def set_feedback(self, fb):
|
||||
"""Change the feedback value in the ui"""
|
||||
if self.status != Status.MOVING:
|
||||
self.fb = fb
|
||||
text = f'{fb:.{int(self.decimals)}f}'
|
||||
text = f"{fb:.{int(self.decimals)}f}"
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
text = text + " " + self.unit
|
||||
self.fb_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def _apply_button_style(self, mode: str):
|
||||
"""Apply a button style depending on if the button shows start or stop"""
|
||||
self.btn_mode = mode
|
||||
if mode == "start":
|
||||
self.btn_action.setText("Move")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
"QPushButton "
|
||||
+ f"{{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else: # stop
|
||||
self.btn_action.setText("Stop")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
"QPushButton "
|
||||
+ f"{{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
def _set_status(self, status: str):
|
||||
"""Set the current status icon in the ui"""
|
||||
self.status = status
|
||||
self.status_icon.set_status(status)
|
||||
|
||||
@@ -370,12 +434,14 @@ class MoveWidget(QWidget):
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
"""Starts or stops motion depending on current situation"""
|
||||
if self._thread and self._thread.isRunning():
|
||||
self._stop_motion()
|
||||
else:
|
||||
self._start_motion()
|
||||
|
||||
def _start_motion(self):
|
||||
"""Start a motion"""
|
||||
target = self.target
|
||||
if abs(target - self.fb) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
@@ -399,21 +465,25 @@ class MoveWidget(QWidget):
|
||||
self._thread.start()
|
||||
|
||||
def _on_error(self):
|
||||
"""Called when an error occurs"""
|
||||
self._set_status(Status.ERROR)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _stop_motion(self):
|
||||
"""Attempts to stop the motion"""
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
|
||||
def _on_position_changed(self, pos: float):
|
||||
"""Change the feedback value in the ui"""
|
||||
self.fb = pos
|
||||
text = f'{pos:.{int(self.decimals)}f}'
|
||||
text = f"{pos:.{int(self.decimals)}f}"
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
text = text + " " + self.unit
|
||||
self.fb_label.setText(text)
|
||||
|
||||
def _on_motion_finished(self, reached: bool):
|
||||
def _on_motion_finished(self):
|
||||
"""Finished a movement"""
|
||||
target = self.target
|
||||
if self.status not in Status.ERROR:
|
||||
if abs(self.fb - target) <= self.deadband:
|
||||
@@ -423,6 +493,7 @@ class MoveWidget(QWidget):
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _cleanup_thread(self):
|
||||
"""Cleaning up of the mover thread"""
|
||||
if self._thread:
|
||||
self._thread.deleteLater()
|
||||
self._thread = None
|
||||
@@ -431,18 +502,20 @@ class MoveWidget(QWidget):
|
||||
self._worker = None
|
||||
|
||||
def shutdown(self):
|
||||
"""Cleaning up of the mover when shutting down the application"""
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
if self._thread:
|
||||
self._thread.quit()
|
||||
self._thread.wait(2000) # max 2 s grace period
|
||||
|
||||
|
||||
class AbsorberWidget(QWidget):
|
||||
"""
|
||||
Control of the frontend absorber (only open)
|
||||
"""
|
||||
|
||||
def __init__(self, absorber, label: str = 'Absorber'):
|
||||
def __init__(self, absorber, label: str = "Absorber"):
|
||||
super().__init__()
|
||||
self.absorber = absorber
|
||||
self.fb = False
|
||||
@@ -460,17 +533,17 @@ class AbsorberWidget(QWidget):
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Blank
|
||||
self.blank_label = QLabel('')
|
||||
self.blank_label = QLabel("")
|
||||
self.blank_label.setFixedWidth(100)
|
||||
layout.addWidget(self.blank_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label = QLabel("-")
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
# Blank icon
|
||||
self.blank_icon = QLabel('')
|
||||
self.blank_icon = QLabel("")
|
||||
self.blank_icon.setFixedWidth(30)
|
||||
self.blank_icon.setContentsMargins(0, 0, 10, 0)
|
||||
layout.addWidget(self.blank_icon)
|
||||
@@ -483,22 +556,31 @@ class AbsorberWidget(QWidget):
|
||||
layout.addWidget(self.btn_action)
|
||||
|
||||
def set_feedback(self, fb: bool):
|
||||
"""
|
||||
Displays the status of the absober in the ui
|
||||
|
||||
Args:
|
||||
fb (bool): True will set the button to Open, False to Closed
|
||||
"""
|
||||
self.fb = fb
|
||||
if fb:
|
||||
self.fb_label.setText('Open')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().success.name()}}}"
|
||||
)
|
||||
self.fb_label.setText("Open")
|
||||
self.fb_label.setStyleSheet(f"QLabel {{color: {get_accent_colors().success.name()}}}")
|
||||
else:
|
||||
self.fb_label.setText('Closed')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().emergency.name()}}}"
|
||||
)
|
||||
self.fb_label.setText("Closed")
|
||||
self.fb_label.setStyleSheet(f"QLabel {{color: {get_accent_colors().emergency.name()}}}")
|
||||
|
||||
def enable_open(self, enable: bool = False):
|
||||
"""
|
||||
Enable or disable the open/close button
|
||||
|
||||
Args:
|
||||
enable (bool): Enables and disables the button
|
||||
"""
|
||||
if enable:
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
"QPushButton "
|
||||
+ f"{{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
self.btn_action.setEnabled(True)
|
||||
else: # disabled
|
||||
@@ -508,4 +590,5 @@ class AbsorberWidget(QWidget):
|
||||
self.btn_action.setDisabled(True)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
"""Open absorber"""
|
||||
self.absorber.open()
|
||||
+80
-46
@@ -1,24 +1,37 @@
|
||||
"""
|
||||
Universal Qt widgets
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QGroupBox, QComboBox, QApplication, QDoubleSpinBox
|
||||
)
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDoubleSpinBox,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
|
||||
class Group(QGroupBox):
|
||||
def __init__(self, label, widgets):
|
||||
super().__init__(label)
|
||||
self.layout = QVBoxLayout(self) # type: ignore
|
||||
self.layout = QVBoxLayout(self) # type: ignore
|
||||
for widget in widgets:
|
||||
self.layout.addWidget(widget) # type: ignore
|
||||
self.layout.addWidget(widget) # type: ignore
|
||||
|
||||
|
||||
class NumberIndicator(QWidget):
|
||||
def __init__(self, label='', unit=None, highlight=False, decimals=3):
|
||||
def __init__(self, label="", unit=None, highlight=False, decimals=3):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
@@ -28,8 +41,8 @@ class NumberIndicator(QWidget):
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
self.val = QLabel('-')
|
||||
self.val.setAlignment(Qt.AlignTop) # type: ignore
|
||||
self.val = QLabel("-")
|
||||
self.val.setAlignment(Qt.AlignTop) # type: ignore
|
||||
# self.val.setFixedWidth(140)
|
||||
layout.addWidget(self.val)
|
||||
self.unit = unit
|
||||
@@ -51,13 +64,25 @@ class NumberIndicator(QWidget):
|
||||
|
||||
def setValue(self, number):
|
||||
self.number = number
|
||||
text = f'{number:.{int(self.decimals)}f}'
|
||||
text = f"{number:.{int(self.decimals)}f}"
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
text = text + " " + self.unit
|
||||
self.val.setText(text)
|
||||
|
||||
|
||||
class InputNumberField(QWidget):
|
||||
def __init__(self, identifier='', label='', unit=None, prefix=None, init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6):
|
||||
def __init__(
|
||||
self,
|
||||
identifier="",
|
||||
label="",
|
||||
unit=None,
|
||||
prefix=None,
|
||||
init=0.0,
|
||||
decimals=1,
|
||||
single_step=0.1,
|
||||
ll=-1e6,
|
||||
hl=1e6,
|
||||
):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
@@ -74,9 +99,9 @@ class InputNumberField(QWidget):
|
||||
self.val.setSingleStep(single_step)
|
||||
self.val.setValue(init)
|
||||
if unit is not None:
|
||||
self.val.setSuffix(' ' + unit)
|
||||
self.val.setSuffix(" " + unit)
|
||||
if prefix is not None:
|
||||
self.val.setPrefix(prefix + ' ')
|
||||
self.val.setPrefix(prefix + " ")
|
||||
# self.val.setFixedWidth(140)
|
||||
layout.addWidget(self.val)
|
||||
|
||||
@@ -85,18 +110,21 @@ class InputNumberField(QWidget):
|
||||
|
||||
def has_focus(self) -> bool:
|
||||
return self.val.hasFocus()
|
||||
|
||||
|
||||
def value(self) -> float:
|
||||
return self.val.value()
|
||||
|
||||
def value_changed_connect(self, func):
|
||||
"""Connect a function to the Enter/Return key press."""
|
||||
self.val.valueChanged.connect(
|
||||
partial(func, identifier=self.identifier, value_obj=self.val, value=lambda: self.val.value())
|
||||
partial(
|
||||
func, identifier=self.identifier, value_obj=self.val, value=lambda: self.val.value()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ComboBox(QWidget):
|
||||
def __init__(self, identifier='', label='', enums=[]):
|
||||
def __init__(self, identifier="", label="", enums=[]):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
@@ -124,14 +152,20 @@ class ComboBox(QWidget):
|
||||
def activated_connect(self, func):
|
||||
"""Connect a function to the Enter/Return key press."""
|
||||
self.value.activated.connect(
|
||||
partial(func, identifier=self.identifier, value_obj=self.value, value=lambda: self.value.currentText())
|
||||
partial(
|
||||
func,
|
||||
identifier=self.identifier,
|
||||
value_obj=self.value,
|
||||
value=lambda: self.value.currentText(),
|
||||
)
|
||||
)
|
||||
|
||||
def setDisabled(self, disable):
|
||||
self.value.setDisabled(disable)
|
||||
|
||||
|
||||
class Button(QWidget):
|
||||
def __init__(self, label=None, label_button:str='', enabled=False):
|
||||
def __init__(self, label=None, label_button: str = "", enabled=False):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
@@ -165,31 +199,31 @@ class Button(QWidget):
|
||||
def setText(self, text):
|
||||
self.button.setText(text)
|
||||
|
||||
# class TextIndicator(QWidget):
|
||||
# def __init__(self, label, unit=None, highlight=False):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.value = QLabel('-')
|
||||
# self.value.setFixedWidth(160)
|
||||
# layout.addWidget(self.value)
|
||||
# self.unit = unit
|
||||
# self.highlight = highlight
|
||||
# if highlight:
|
||||
# font = QFont()
|
||||
# font.setBold(True)
|
||||
# font.setPointSize(14)
|
||||
# self.label.setFont(font)
|
||||
# self.value.setFont(font)
|
||||
|
||||
# def set_text(self, text):
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.value.setText(text)
|
||||
class TextIndicator(QWidget):
|
||||
def __init__(self, label):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
self.text = QLabel("-")
|
||||
self.text.setAlignment(Qt.AlignTop) # type: ignore
|
||||
layout.addWidget(self.text)
|
||||
|
||||
def setLabel(self, label) -> None:
|
||||
self.label.setText(label)
|
||||
|
||||
def setText(self, text):
|
||||
self.text.setText(text)
|
||||
|
||||
def setColor(self, color: str):
|
||||
self.text.setStyleSheet(f"QLabel {{color:{color}}}")
|
||||
|
||||
|
||||
# class Button(QWidget):
|
||||
# def __init__(self, label, label_button):
|
||||
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
X01DA / Debye Beamline Parameters.
|
||||
This file describes the parameter of each component of the Debye beamline
|
||||
to be used for raytracing and geometrical calculations.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import xrt.backends.raycing.materials as rm
|
||||
|
||||
# XRT definitions
|
||||
filterBeryl = rm.Material("Be", rho=1.85, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterDiamond = rm.Material("C", rho=3.52, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material("C", rho=2.266, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
|
||||
stripeSi = rm.Material("Si", rho=2.33) # pyright: ignore[reportArgumentType]
|
||||
stripePt = rm.Material("Pt", rho=21.45) # pyright: ignore[reportArgumentType]
|
||||
stripeRh = rm.Material("Rh", rho=12.41) # pyright: ignore[reportArgumentType]
|
||||
stripeCr = rm.Material("Cr", rho=7.14) # pyright: ignore[reportArgumentType]
|
||||
stripePyrex = rm.Material(
|
||||
"Si", rho=2.20
|
||||
) # Use Si as bare element and the density of SiO2 # pyright: ignore[reportArgumentType]
|
||||
|
||||
si111_1 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # first xtal surface
|
||||
si311_1 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # first xtal surface
|
||||
si333_1 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # first xtal surface
|
||||
si511_1 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # first xtal surface
|
||||
si111_2 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # second xtal surface
|
||||
si311_2 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # second xtal surface
|
||||
si333_2 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # second xtal surface
|
||||
si511_2 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # second xtal surface
|
||||
|
||||
filterDiamond = rm.Material("C", rho=3.52, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterBe = rm.Material("Be", rho=1.85, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterSi3N4 = rm.Material(
|
||||
["Si", "N"], quantities=[3, 4], rho=3.44, kind="plate"
|
||||
) # pyright: ignore[reportArgumentType]
|
||||
filterAl = rm.Material("Al", rho=2.69, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material("C", rho=2.266, kind="plate") # pyright: ignore[reportArgumentType]
|
||||
|
||||
# General parameters
|
||||
sourceHeight = 0
|
||||
|
||||
# Synchrotron
|
||||
synchrotron = namedtuple(
|
||||
"synchrotron", ["eE", "eI", "eEspread", "eEpsilonX", "eEpsilonZ", "betaX", "betaZ"]
|
||||
)
|
||||
|
||||
sls1 = synchrotron(
|
||||
eE=2.4, eI=0.4, eEspread=0.878e-3, eEpsilonX=5.63, eEpsilonZ=0.007, betaX=0.45, betaZ=14.4
|
||||
)
|
||||
|
||||
sls2 = synchrotron(
|
||||
eE=2.7, eI=0.4, eEspread=1.147e-3, eEpsilonX=0.156, eEpsilonZ=0.01, betaX=0.18, betaZ=4.6
|
||||
)
|
||||
|
||||
# Source
|
||||
bendingMagnet = namedtuple("bendingMagnet", ["name", "center", "sync", "B0"])
|
||||
|
||||
sls1_14t = bendingMagnet(name="FE-BM-SLS1-1.4T", center=(0, 0, 0), sync=sls1, B0=1.4)
|
||||
|
||||
sls2_21t = bendingMagnet(name="FE-BM-SLS2-2.1T", center=(0, 0, 0), sync=sls2, B0=2.1)
|
||||
|
||||
sls2_35t = bendingMagnet(name="FE-BM-SLS2-3.5T", center=(0, 0, 0), sync=sls2, B0=3.5)
|
||||
|
||||
sls2_50t = bendingMagnet(name="FE-BM-SLS2-5.0T", center=(0, 0, 0), sync=sls2, B0=5.0)
|
||||
|
||||
# FE slits
|
||||
fe_slits = namedtuple("slits", ["name", "center", "center1", "center2", "maxDivH", "maxDivV"])
|
||||
|
||||
feSlits = fe_slits(
|
||||
name="FE-SLITS",
|
||||
center=(0, 6117, sourceHeight),
|
||||
center1=(0, 5045, sourceHeight),
|
||||
center2=(0, 5289.5, sourceHeight),
|
||||
maxDivH=1.8e-3,
|
||||
maxDivV=0.8e-3,
|
||||
)
|
||||
|
||||
# FE Window
|
||||
filt = namedtuple(
|
||||
"filt", ["name", "center", "pitch", "limPhysX", "limPhysY", "surface", "material", "thickness"]
|
||||
)
|
||||
|
||||
feWindow = filt(
|
||||
name="FE-WINDOW",
|
||||
center=(0.0, 7020, sourceHeight),
|
||||
pitch=np.pi / 2,
|
||||
limPhysX=(-6, 6),
|
||||
limPhysY=(-3.0, 3.0),
|
||||
surface="None",
|
||||
material=filterDiamond,
|
||||
thickness=0.1,
|
||||
)
|
||||
feWindow = feWindow._replace(surface=f"CVD Diamond window {feWindow.thickness*1e3:0.0f} $\\mu$m")
|
||||
|
||||
# Collimating mirror
|
||||
collimatingMirror = namedtuple(
|
||||
"collimatingMirror",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"surface",
|
||||
"material",
|
||||
"limPhysX",
|
||||
"limPhysY",
|
||||
"limOptX",
|
||||
"limOptY",
|
||||
"R",
|
||||
"pitch",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx1",
|
||||
"tx2",
|
||||
],
|
||||
)
|
||||
|
||||
cm = collimatingMirror(
|
||||
name="FE-CM",
|
||||
center=[0, 6890, sourceHeight],
|
||||
surface=("Si", "Pt", "Rh"),
|
||||
material=(stripeSi, stripePt, stripeRh),
|
||||
limPhysX=(-34, 34),
|
||||
limPhysY=(-600, 600),
|
||||
limOptX=((-21, -7, 14), (-11, 11, 23)),
|
||||
limOptY=((-500, -500, -500), (500, 500, 500)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, -0.0e-3],
|
||||
jack1=[0.0, 7210.0, 0.0], # Tripod X, Y, Z (global)
|
||||
jack2=[-210.0, 8310.0, 0.0],
|
||||
jack3=[210.0, 8310.0, 0.0],
|
||||
tx1=[0.0, -575.5], # X-Stage 1 [x, y] (local)
|
||||
tx2=[0.0, 575],
|
||||
) # X-Stage 2
|
||||
|
||||
apertures = namedtuple("apertures", ["name", "center", "opening"])
|
||||
|
||||
fePS = apertures(
|
||||
name="FE-PS", center=[0, 8815, sourceHeight], opening=[-20.0, 20.0, -20.0 + 12.5, 20.0 + 12.5]
|
||||
) # left, right, bottom, top
|
||||
|
||||
opWbBsBlock = apertures(
|
||||
name="OP-WB-BS-BLOCK", center=[0.0, 13860, sourceHeight], opening=[-18.0, 18.0, 25, 85.5]
|
||||
) # left, right, bottom, top
|
||||
# opening=[-18., 18., 42, 76], # X10DA
|
||||
|
||||
# Monochromator
|
||||
monochromator = namedtuple(
|
||||
"monochromator",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"xtal",
|
||||
"material1",
|
||||
"material2",
|
||||
"xtalWidth",
|
||||
"xtalOffsetX",
|
||||
"xtalLength1",
|
||||
"xtalLength2",
|
||||
"xtalGap",
|
||||
"rotOffset",
|
||||
"heightOffset",
|
||||
"braggLim",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx",
|
||||
],
|
||||
)
|
||||
|
||||
mo1 = monochromator(
|
||||
name="OP-MO1",
|
||||
center=[0.0, 11750, sourceHeight],
|
||||
xtal=("Si311", "Si111"),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth=(24, 24),
|
||||
xtalOffsetX=(-21.2, 21.2),
|
||||
xtalLength1=(55, 55),
|
||||
xtalLength2=(105, 105),
|
||||
xtalGap=(8, 8),
|
||||
rotOffset=6,
|
||||
heightOffset=8.5,
|
||||
braggLim=[3.6, 33],
|
||||
jack1=[0.0, 11350.0, 0.0], # Tripod maybe not available!
|
||||
jack2=[-400.0, 12350.0, 0.0],
|
||||
jack3=[400.0, 12350.0, 0.0],
|
||||
tx=0.0,
|
||||
) # X-Stage [x]
|
||||
|
||||
mo2 = monochromator(
|
||||
name="OP-CCM2",
|
||||
center=[0.0, 13250, sourceHeight],
|
||||
xtal=("Si311", "Si111"),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth=(24, 24),
|
||||
xtalOffsetX=(-21, 21),
|
||||
xtalLength1=(55, 55),
|
||||
xtalLength2=(105, 105),
|
||||
xtalGap=(8, 8),
|
||||
rotOffset=6,
|
||||
heightOffset=8.5,
|
||||
braggLim=[3.6, 33],
|
||||
jack1=[0.0, 13350.0, 0.0], # Tripod maybe not available!
|
||||
jack2=[-400.0, 14350.0, 0.0],
|
||||
jack3=[400.0, 14350.0, 0.0],
|
||||
tx=0.0,
|
||||
) # X-Stage [x]
|
||||
|
||||
# OP Slits
|
||||
op_slits = namedtuple("op_slits", ["name", "center"])
|
||||
|
||||
opSlits1 = op_slits(name="OP-SLITS 1", center=(0, 14349.6, sourceHeight))
|
||||
|
||||
opSlits2 = op_slits(name="OP-SLITS 2", center=(0, 18134.8, sourceHeight))
|
||||
|
||||
# OP Beam Monitors
|
||||
op_bm = namedtuple("op_bm", ["name", "center"])
|
||||
|
||||
opBM1 = op_bm(name="OP Beam Monitor 1", center=(0, 14599.6, sourceHeight))
|
||||
|
||||
opBM2 = op_bm(name="OP Beam Monitor 2", center=(0, 18384.8, sourceHeight))
|
||||
|
||||
# Focusing mirror
|
||||
focusingMirror = namedtuple(
|
||||
"focusingMirror",
|
||||
[
|
||||
"name",
|
||||
"center",
|
||||
"surfaceToroid",
|
||||
"materialToroid",
|
||||
"surfaceFlat",
|
||||
"materialFlat",
|
||||
"limPhysXToroid",
|
||||
"limPhysYToroid",
|
||||
"limPhysXFlat",
|
||||
"limPhysYFlat",
|
||||
"limOptXToroid",
|
||||
"limOptYToroid",
|
||||
"limOptXFlat",
|
||||
"limOptYFlat",
|
||||
"R",
|
||||
"pitch",
|
||||
"r",
|
||||
"xToroid",
|
||||
"xFlat",
|
||||
"hToroid",
|
||||
"jack1",
|
||||
"jack2",
|
||||
"jack3",
|
||||
"tx1",
|
||||
"tx2",
|
||||
],
|
||||
)
|
||||
|
||||
fm = focusingMirror(
|
||||
name="OP-FM",
|
||||
center=[0.0, 15670, sourceHeight], # nominal height 58 mm above ring, SLS1!
|
||||
surfaceToroid=("Rh", "Pt"),
|
||||
materialToroid=(stripeRh, stripePt),
|
||||
surfaceFlat=("Rh", "Pt"),
|
||||
materialFlat=(stripeRh, stripePt),
|
||||
limPhysXToroid=(-79.0, 79.0),
|
||||
limPhysYToroid=(-575.0, 575.0),
|
||||
limPhysXFlat=(-79.0, 79.0),
|
||||
limPhysYFlat=(-575.0, 575.0),
|
||||
limOptXToroid=((-38, 66), (-66, 31)),
|
||||
limOptYToroid=((-500.0, -500.0), (500.0, 500.0)),
|
||||
limOptXFlat=((-11.45, 23.55), (-30.45, -6.45)),
|
||||
limOptYFlat=((-500.0, -500.0), (500.0, 500.0)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, 0e-3],
|
||||
r=[35.510, 24.986],
|
||||
xToroid=[-52, 48.5], # offset in local x
|
||||
xFlat=[-20.95, 8.55],
|
||||
hToroid=[2.88, 7.15], # depth of the cylinder at x = xCylinder1 and x = xCylinder2.
|
||||
jack1=[-130.0, 15535 - 538.0, 0.0],
|
||||
jack2=[130.0, 15535 + 538.0, 0.0],
|
||||
jack3=[0.0, 15535 + 538.0, 0.0],
|
||||
tx1=[0.0, -575.0], # X-Stage 1 [x, y]
|
||||
tx2=[0.0, 575.0],
|
||||
) # X-Stage 2 [x, y]
|
||||
|
||||
# EH Window
|
||||
ehWindow = filt(
|
||||
name="EH-WINDOW",
|
||||
center=(0.0, 19998.3, sourceHeight),
|
||||
pitch=np.pi / 2,
|
||||
limPhysX=(-20.0, 20.0),
|
||||
limPhysY=(-4, 4),
|
||||
surface="None",
|
||||
material=filterSi3N4,
|
||||
thickness=0.002,
|
||||
)
|
||||
ehWindow = ehWindow._replace(surface=f"Beryllium window {ehWindow.thickness*1e3:0.0f} $\\mu$m")
|
||||
|
||||
# Sample
|
||||
sample = namedtuple("sample", ["name", "center"])
|
||||
|
||||
smpl = sample(name="EH-SMPL", center=[0, 23365, sourceHeight])
|
||||
|
||||
smpl2 = sample(name="EH-SMPL2", center=[0, 27500, sourceHeight])
|
||||
|
||||
# Vacuum pipes
|
||||
# DN40CF ID = 35 mm oder 37 mm
|
||||
# DN50CF ID = 47.5 mm
|
||||
# DN63CF ID = 60.2 mm oder 66 mm
|
||||
# DN100CF ID = 97.4 mm oder 104 mm
|
||||
pipe = namedtuple("pipes", ["center", "diameter", "start", "end"])
|
||||
vacuum_pipes = pipe(
|
||||
center=[27.5, (37.5 + 27.5) / 2, 37.5, 62.5, 72.5],
|
||||
diameter=[97.4, 97.4, 97.4, 97.4, 97.4],
|
||||
start=[10952.88, 11750 + 250, mo2.center[1] + 250, 14000, fm.center[1]],
|
||||
end=[11750 - 250, mo2.center[1] - 250, 14000, fm.center[1], ehWindow.center[1]],
|
||||
)
|
||||
|
||||
Walls = namedtuple("walls", ["start", "end", "height"])
|
||||
walls = Walls(start=[13999.30], end=[13999 + 75.5 + 30], height=[[-20, 25]])
|
||||
@@ -1,311 +0,0 @@
|
||||
"""
|
||||
X01DA / Debye Beamline Parameters.
|
||||
This file describes the parameter of each component of the Debye beamline
|
||||
to be used for raytracing and geometrical calculations.
|
||||
"""
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
from collections import namedtuple
|
||||
|
||||
import xrt.backends.raycing.materials as rm
|
||||
|
||||
# if os.environ.get("USE_XRT", "True").lower() in ("1", "true", "yes"):
|
||||
# import xrt.backends.raycing.materials as rm # type: ignore
|
||||
# else:
|
||||
# class _DummyClass:
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# pass
|
||||
# class _DummyMaterials:
|
||||
# Material = _DummyClass
|
||||
# CrystalSi = _DummyClass
|
||||
# rm = _DummyMaterials()
|
||||
|
||||
# XRT definitions
|
||||
filterBeryl = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
|
||||
stripeSi = rm.Material('Si', rho=2.33) # pyright: ignore[reportArgumentType]
|
||||
stripePt = rm.Material('Pt', rho=21.45) # pyright: ignore[reportArgumentType]
|
||||
stripeRh = rm.Material('Rh', rho=12.41) # pyright: ignore[reportArgumentType]
|
||||
stripeCr = rm.Material('Cr', rho=7.14) # pyright: ignore[reportArgumentType]
|
||||
stripePyrex = rm.Material('Si', rho=2.20) # Use Si as bare element and the density of SiO2 # pyright: ignore[reportArgumentType]
|
||||
|
||||
si111_1 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # first xtal surface
|
||||
si311_1 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # first xtal surface
|
||||
si333_1 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # first xtal surface
|
||||
si511_1 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # first xtal surface
|
||||
si111_2 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # second xtal surface
|
||||
si311_2 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # second xtal surface
|
||||
si333_2 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # second xtal surface
|
||||
si511_2 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # second xtal surface
|
||||
|
||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterBe = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterSi3N4 = rm.Material(['Si', 'N'], quantities=[3, 4], rho=3.44, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterAl = rm.Material('Al', rho=2.69, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
|
||||
# General parameters
|
||||
sourceHeight = 0
|
||||
|
||||
#Synchrotron
|
||||
synchrotron = namedtuple('synchrotron', ['eE', 'eI', 'eEspread',
|
||||
'eEpsilonX', 'eEpsilonZ', 'betaX', 'betaZ'])
|
||||
|
||||
sls1 = synchrotron(
|
||||
eE = 2.4,
|
||||
eI = 0.4,
|
||||
eEspread=0.878e-3,
|
||||
eEpsilonX=5.63,
|
||||
eEpsilonZ=0.007,
|
||||
betaX=0.45,
|
||||
betaZ=14.4,
|
||||
)
|
||||
|
||||
sls2 = synchrotron(
|
||||
eE=2.7,
|
||||
eI=0.4,
|
||||
eEspread=1.147e-3,
|
||||
eEpsilonX=0.156,
|
||||
eEpsilonZ=0.01,
|
||||
betaX=0.18,
|
||||
betaZ=4.6,
|
||||
)
|
||||
|
||||
# Source
|
||||
bendingMagnet = namedtuple('bendingMagnet', ['name', 'center', 'sync', 'B0'])
|
||||
|
||||
sls1_14t = bendingMagnet(
|
||||
name='FE-BM-SLS1-1.4T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls1,
|
||||
B0=1.4,)
|
||||
|
||||
sls2_21t = bendingMagnet(
|
||||
name='FE-BM-SLS2-2.1T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=2.1,)
|
||||
|
||||
sls2_35t = bendingMagnet(
|
||||
name='FE-BM-SLS2-3.5T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=3.5,)
|
||||
|
||||
sls2_50t = bendingMagnet(
|
||||
name='FE-BM-SLS2-5.0T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=5.0,)
|
||||
|
||||
# FE slits
|
||||
fe_slits = namedtuple('slits', ['name', 'center', 'center1', 'center2', 'maxDivH', 'maxDivV'])
|
||||
|
||||
feSlits = fe_slits(
|
||||
name='FE-SLITS',
|
||||
center=(0, 6117, sourceHeight),
|
||||
center1=(0, 5045, sourceHeight),
|
||||
center2=(0, 5289.5, sourceHeight),
|
||||
maxDivH=1.8e-3,
|
||||
maxDivV=0.8e-3,)
|
||||
|
||||
# FE Window
|
||||
filt = namedtuple('filt', ['name', 'center', 'pitch', 'limPhysX', 'limPhysY', 'surface', 'material', 'thickness'])
|
||||
|
||||
feWindow = filt(
|
||||
name='FE-WINDOW',
|
||||
center=(0., 7020, sourceHeight),
|
||||
pitch=np.pi/2,
|
||||
limPhysX=(-6, 6),
|
||||
limPhysY=(-3., 3.),
|
||||
surface='None',
|
||||
material=filterDiamond,
|
||||
thickness=0.1,)
|
||||
feWindow = feWindow._replace(surface=f'CVD Diamond window {feWindow.thickness*1e3:0.0f} $\\mu$m')
|
||||
|
||||
# Collimating mirror
|
||||
collimatingMirror = namedtuple('collimatingMirror', ['name',
|
||||
'center', 'surface', 'material', 'limPhysX', 'limPhysY',
|
||||
'limOptX', 'limOptY', 'R', 'pitch', 'jack1', 'jack2', 'jack3',
|
||||
'tx1', 'tx2'])
|
||||
|
||||
cm = collimatingMirror(
|
||||
name='FE-CM',
|
||||
center=[0, 6890, sourceHeight],
|
||||
surface=('Si','Pt','Rh'),
|
||||
material=(stripeSi, stripePt, stripeRh),
|
||||
limPhysX=(-34, 34),
|
||||
limPhysY=(-600, 600),
|
||||
limOptX=((-21, -7, 14), (-11, 11, 23)),
|
||||
limOptY=((-500, -500, -500), (500, 500, 500)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, -0.0e-3],
|
||||
jack1=[0., 7210., 0.], #Tripod X, Y, Z (global)
|
||||
jack2=[-210., 8310., 0.],
|
||||
jack3=[210., 8310., 0.],
|
||||
tx1=[0.0, -575.5], # X-Stage 1 [x, y] (local)
|
||||
tx2=[0.0, 575],) # X-Stage 2
|
||||
|
||||
apertures = namedtuple('apertures', ['name', 'center', 'opening'])
|
||||
|
||||
fePS = apertures(
|
||||
name='FE-PS',
|
||||
center=[0, 8815, sourceHeight],
|
||||
opening=[-20., 20., -20.+12.5, 20.+12.5]) # left, right, bottom, top
|
||||
|
||||
opWbBsBlock = apertures(
|
||||
name='OP-WB-BS-BLOCK',
|
||||
center=[0., 13860, sourceHeight],
|
||||
opening=[-18., 18., 25, 85.5]) # left, right, bottom, top
|
||||
# opening=[-18., 18., 42, 76], # X10DA
|
||||
|
||||
# Monochromator
|
||||
monochromator = namedtuple('monochromator', ['name', 'center',
|
||||
'xtal', 'material1', 'material2', 'xtalWidth', 'xtalOffsetX',
|
||||
'xtalLength1', 'xtalLength2', 'xtalGap', 'rotOffset',
|
||||
'heightOffset', 'braggLim', 'jack1', 'jack2', 'jack3', 'tx'])
|
||||
|
||||
mo1 = monochromator(
|
||||
name='OP-MO1',
|
||||
center=[0., 11750, sourceHeight],
|
||||
xtal=('Si311','Si111'),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth = (24, 24),
|
||||
xtalOffsetX=(-21.2, 21.2),
|
||||
xtalLength1 = (55, 55),
|
||||
xtalLength2 = (105, 105),
|
||||
xtalGap = (8, 8),
|
||||
rotOffset = 6,
|
||||
heightOffset = 8.5,
|
||||
braggLim = [3.6, 33],
|
||||
jack1=[0., 11350., 0.], #Tripod maybe not available!
|
||||
jack2=[-400., 12350., 0.],
|
||||
jack3=[400., 12350., 0.],
|
||||
tx=0.0,) # X-Stage [x]
|
||||
|
||||
mo2 = monochromator(
|
||||
name='OP-CCM2',
|
||||
center=[0., 13250, sourceHeight],
|
||||
xtal=('Si311','Si111'),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth = (24, 24),
|
||||
xtalOffsetX=(-21, 21),
|
||||
xtalLength1 = (55, 55),
|
||||
xtalLength2 = (105, 105),
|
||||
xtalGap = (8, 8),
|
||||
rotOffset = 6,
|
||||
heightOffset = 8.5,
|
||||
braggLim = [3.6, 33],
|
||||
jack1=[0., 13350., 0.], #Tripod maybe not available!
|
||||
jack2=[-400., 14350., 0.],
|
||||
jack3=[400., 14350., 0.],
|
||||
tx=0.0,) # X-Stage [x]
|
||||
|
||||
# OP Slits
|
||||
op_slits = namedtuple('op_slits', ['name', 'center'])
|
||||
|
||||
opSlits1 = op_slits(
|
||||
name='OP-SLITS 1',
|
||||
center=(0, 14349.6, sourceHeight),
|
||||
)
|
||||
|
||||
opSlits2 = op_slits(
|
||||
name='OP-SLITS 2',
|
||||
center=(0, 18134.8, sourceHeight),
|
||||
)
|
||||
|
||||
# OP Beam Monitors
|
||||
op_bm = namedtuple('op_bm', ['name', 'center'])
|
||||
|
||||
opBM1 = op_bm(
|
||||
name='OP Beam Monitor 1',
|
||||
center=(0, 14599.6, sourceHeight),
|
||||
)
|
||||
|
||||
opBM2 = op_bm(
|
||||
name='OP Beam Monitor 2',
|
||||
center=(0, 18384.8, sourceHeight),
|
||||
)
|
||||
|
||||
# Focusing mirror
|
||||
focusingMirror = namedtuple('focusingMirror', ['name', 'center',
|
||||
'surfaceToroid', 'materialToroid', 'surfaceFlat', 'materialFlat',
|
||||
'limPhysXToroid', 'limPhysYToroid', 'limPhysXFlat', 'limPhysYFlat',
|
||||
'limOptXToroid', 'limOptYToroid', 'limOptXFlat', 'limOptYFlat',
|
||||
'R', 'pitch', 'r', 'xToroid', 'xFlat', 'hToroid', 'jack1', 'jack2', 'jack3',
|
||||
'tx1', 'tx2'])
|
||||
|
||||
fm = focusingMirror(
|
||||
name='OP-FM',
|
||||
center=[0., 15670, sourceHeight], # nominal height 58 mm above ring, SLS1!
|
||||
surfaceToroid=('Rh', 'Pt'),
|
||||
materialToroid=(stripeRh, stripePt),
|
||||
surfaceFlat=('Rh', 'Pt'),
|
||||
materialFlat=(stripeRh, stripePt),
|
||||
limPhysXToroid=(-79., 79.),
|
||||
limPhysYToroid=(-575., 575.),
|
||||
limPhysXFlat=(-79., 79.),
|
||||
limPhysYFlat=(-575., 575.),
|
||||
limOptXToroid=((-38, 66), (-66, 31)),
|
||||
limOptYToroid=((-500., -500.), (500., 500.)),
|
||||
limOptXFlat=((-11.45, 23.55), (-30.45, -6.45)),
|
||||
limOptYFlat=((-500., -500.), (500., 500.)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, 0e-3],
|
||||
r=[35.510, 24.986],
|
||||
xToroid=[-52, 48.5], # offset in local x
|
||||
xFlat = [-20.95, 8.55],
|
||||
hToroid=[2.88, 7.15], # depth of the cylinder at x = xCylinder1 and x = xCylinder2.
|
||||
jack1=[-130., 15535-538., 0.],
|
||||
jack2=[130., 15535+538., 0.],
|
||||
jack3=[0., 15535+538., 0.],
|
||||
tx1=[0., -575.], # X-Stage 1 [x, y]
|
||||
tx2=[0., 575.],) # X-Stage 2 [x, y]
|
||||
|
||||
# EH Window
|
||||
ehWindow = filt(
|
||||
name='EH-WINDOW',
|
||||
center=(0., 19998.3, sourceHeight),
|
||||
pitch=np.pi/2,
|
||||
limPhysX=(-20., 20.),
|
||||
limPhysY=(-4, 4),
|
||||
surface='None',
|
||||
material=filterSi3N4,
|
||||
thickness=0.002,)
|
||||
ehWindow = ehWindow._replace(surface=f'Beryllium window {ehWindow.thickness*1e3:0.0f} $\\mu$m')
|
||||
|
||||
# Sample
|
||||
sample = namedtuple('sample', ['name', 'center'])
|
||||
|
||||
smpl = sample(
|
||||
name='EH-SMPL',
|
||||
center=[0, 23365, sourceHeight],)
|
||||
|
||||
smpl2 = sample(
|
||||
name='EH-SMPL2',
|
||||
center=[0, 27500, sourceHeight],)
|
||||
|
||||
# Vacuum pipes
|
||||
# DN40CF ID = 35 mm oder 37 mm
|
||||
# DN50CF ID = 47.5 mm
|
||||
# DN63CF ID = 60.2 mm oder 66 mm
|
||||
# DN100CF ID = 97.4 mm oder 104 mm
|
||||
pipe = namedtuple('pipes', ['center', 'diameter', 'start', 'end'])
|
||||
vacuum_pipes = pipe(
|
||||
center= [27.5, (37.5+27.5)/2, 37.5, 62.5, 72.5],
|
||||
diameter=[97.4, 97.4, 97.4, 97.4, 97.4],
|
||||
start= [10952.88, 11750+250, mo2.center[1]+250, 14000, fm.center[1]],
|
||||
end= [11750-250, mo2.center[1]-250, 14000, fm.center[1], ehWindow.center[1]],
|
||||
)
|
||||
|
||||
Walls = namedtuple('walls', ['start', 'end', 'height'])
|
||||
walls = Walls(
|
||||
start= [13999.30],
|
||||
end= [13999+75.5+30],
|
||||
height= [[-20, 25]],
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
from bec_server.file_writer.default_writer import DefaultFormat
|
||||
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
import debye_bec.bec_widgets.widgets.digital_twin.x01da_parameters as bl
|
||||
|
||||
|
||||
class DebyeNexusStructure(DefaultFormat):
|
||||
"""Nexus Structure for Debye"""
|
||||
@@ -31,8 +32,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
|
||||
if "curr" in self.device_manager.devices:
|
||||
ring_current = source.create_soft_link(
|
||||
name="ring_current",
|
||||
target="/entry/collection/devices/curr/curr/value",
|
||||
name="ring_current", target="/entry/collection/devices/curr/curr/value"
|
||||
)
|
||||
ring_current.attrs["NX_class"] = "NX_FLOAT"
|
||||
ring_current.attrs["units"] = "mA"
|
||||
@@ -57,12 +57,12 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
name="reflection",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_xtal_string/value",
|
||||
)
|
||||
reflection.attrs["NX_class"] = "NX_CHAR"
|
||||
reflection.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
# Create a softlink
|
||||
d_spacing = crystal.create_soft_link(
|
||||
name="d_spacing",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_d_spacing/value",
|
||||
name="d_spacing",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_d_spacing/value",
|
||||
)
|
||||
d_spacing.attrs["NX_class"] = "NX_FLOAT"
|
||||
d_spacing.attrs["units"] = "angstrom"
|
||||
@@ -71,40 +71,40 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
name="bragg_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_bragg_off/value",
|
||||
)
|
||||
bragg_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
bragg_offset.attrs["units"] = "degree"
|
||||
bragg_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
bragg_offset.attrs["units"] = "degree"
|
||||
|
||||
phi_offset = crystal.create_soft_link(
|
||||
name="phi_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_phi_off/value",
|
||||
)
|
||||
phi_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
phi_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
phi_offset.attrs["units"] = "degree"
|
||||
|
||||
## Logic if device exist
|
||||
if "mo1_roty" in self.device_manager.devices:
|
||||
if "mo1_roty" in self.device_manager.devices:
|
||||
|
||||
# Create a softlink
|
||||
azimuthal_angle = crystal.create_soft_link(
|
||||
name="azimuthal_angle",
|
||||
target="/entry/collection/devices/mo1_roty/mo1_roty/value",
|
||||
name="azimuthal_angle",
|
||||
target="/entry/collection/devices/mo1_roty/mo1_roty/value",
|
||||
)
|
||||
azimuthal_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
azimuthal_angle.attrs["units"] = "degree"
|
||||
|
||||
azimuthal_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
azimuthal_angle.attrs["units"] = "degree"
|
||||
|
||||
azm_offset = crystal.create_soft_link(
|
||||
name="azm_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_azm_off/value",
|
||||
)
|
||||
azm_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
azm_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
azm_offset.attrs["units"] = "degree"
|
||||
|
||||
miscut = crystal.create_soft_link(
|
||||
name="miscut",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_miscut/value",
|
||||
)
|
||||
miscut.attrs["NX_class"] = "NX_FLOAT"
|
||||
miscut.attrs["units"] = "degree"
|
||||
miscut.attrs["NX_class"] = "NX_FLOAT"
|
||||
miscut.attrs["units"] = "degree"
|
||||
|
||||
###################
|
||||
### cm mirror specific information
|
||||
@@ -118,7 +118,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
)
|
||||
cm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
#previous error due to space in name field
|
||||
# previous error due to space in name field
|
||||
|
||||
if "cm_bnd_radius" in self.device_manager.devices:
|
||||
cm_bending_radius = collimating_mirror.create_soft_link(
|
||||
@@ -149,15 +149,15 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
cm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_roll_angle.attrs["units"] = "mrad"
|
||||
|
||||
if 'cm_trx' in self.device_manager.devices:
|
||||
cm_trx = - self.device_manager.devices.cm_trx.read(cached=True).get('cm_trx').get('value')
|
||||
stripe = 'Unknown'
|
||||
if "cm_trx" in self.device_manager.devices:
|
||||
cm_trx = (
|
||||
-self.device_manager.devices.cm_trx.read(cached=True).get("cm_trx").get("value")
|
||||
)
|
||||
stripe = "Unknown"
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if low <= cm_trx <= high:
|
||||
stripe = name
|
||||
cm_stripe = collimating_mirror.create_dataset(
|
||||
name="stripe", data=stripe
|
||||
)
|
||||
cm_stripe = collimating_mirror.create_dataset(name="stripe", data=stripe)
|
||||
cm_stripe.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
###################
|
||||
@@ -167,9 +167,7 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
focusing_mirror = instrument.create_group(name="focusing_mirror")
|
||||
focusing_mirror.attrs["NX_class"] = "NXmirror"
|
||||
|
||||
fm_substrate_material = focusing_mirror.create_dataset(
|
||||
name="substrate_material", data="Si"
|
||||
)
|
||||
fm_substrate_material = focusing_mirror.create_dataset(name="substrate_material", data="Si")
|
||||
fm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if "fm_bnd_radius" in self.device_manager.devices:
|
||||
@@ -201,18 +199,22 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
fm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
fm_roll_angle.attrs["units"] = "mrad"
|
||||
|
||||
if 'fm_trx' in self.device_manager.devices:
|
||||
fm_trx = - self.device_manager.devices.fm_trx.read(cached=True).get('fm_trx').get('value')
|
||||
stripe = 'Unknown'
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + ' (flat)'
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + ' (toroid)'
|
||||
fm_stripe = focusing_mirror.create_dataset(
|
||||
name="stripe", data=stripe
|
||||
if "fm_trx" in self.device_manager.devices:
|
||||
fm_trx = (
|
||||
-self.device_manager.devices.fm_trx.read(cached=True).get("fm_trx").get("value")
|
||||
)
|
||||
stripe = "Unknown"
|
||||
for name, low, high in zip(
|
||||
bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]
|
||||
):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + " (flat)"
|
||||
for name, low, high in zip(
|
||||
bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]
|
||||
):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + " (toroid)"
|
||||
fm_stripe = focusing_mirror.create_dataset(name="stripe", data=stripe)
|
||||
fm_stripe.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
###################
|
||||
@@ -220,45 +222,65 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
###################
|
||||
|
||||
## Logic if device exist
|
||||
if "nidaq" in self.device_manager.devices:
|
||||
|
||||
#ai_chans_bits = self.device_manager.devices.nidaq.ai_chans.read(cached=True).get("nidaq_ai_chans").get("value")
|
||||
ai_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ai_chans", {}).get("value")
|
||||
ci_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ci_chans", {}).get("value")
|
||||
#add_chans_bits = self.device_manager.devices.nidaq.add_chans.read(cached=True).get("nidaq_add_chans").get("value")
|
||||
add_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_add_chans", {}).get("value")
|
||||
if "nidaq" in self.device_manager.devices:
|
||||
|
||||
# ai_chans_bits = self.device_manager.devices.nidaq.ai_chans.read(cached=True).get("nidaq_ai_chans").get("value")
|
||||
ai_chans_bits = (
|
||||
self.configuration.get("nidaq", {}).get("nidaq_ai_chans", {}).get("value")
|
||||
)
|
||||
ci_chans_bits = (
|
||||
self.configuration.get("nidaq", {}).get("nidaq_ci_chans", {}).get("value")
|
||||
)
|
||||
# add_chans_bits = self.device_manager.devices.nidaq.add_chans.read(cached=True).get("nidaq_add_chans").get("value")
|
||||
add_chans_bits = (
|
||||
self.configuration.get("nidaq", {}).get("nidaq_add_chans", {}).get("value")
|
||||
)
|
||||
|
||||
measurement_mode = entry.create_group(name="mode")
|
||||
measurement_mode.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if (int(ci_chans_bits) & 0x7F) != 0:
|
||||
# Create a dataset
|
||||
rayspec_sdd_active = measurement_mode.create_group(name="Multi_Element_Partial_Fluorescence_Yield")
|
||||
me_sdd = rayspec_sdd_active.create_dataset(name="Detector", data="Rayspec 7 element Silicon Drift Detector")
|
||||
rayspec_sdd_active = measurement_mode.create_group(
|
||||
name="Multi_Element_Partial_Fluorescence_Yield"
|
||||
)
|
||||
me_sdd = rayspec_sdd_active.create_dataset(
|
||||
name="Detector", data="Rayspec 7 element Silicon Drift Detector"
|
||||
)
|
||||
me_sdd.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if (int(ci_chans_bits) & (1<<8)) != 0:
|
||||
if (int(ci_chans_bits) & (1 << 8)) != 0:
|
||||
# Create a dataset
|
||||
ketek_sdd_active = measurement_mode.create_group(name="Single_Element_Partial_Fluorescence_Yield")
|
||||
se_sdd = ketek_sdd_active.create_dataset(name="Detector", data="Ketex mini single element Silicon Drift Detector")
|
||||
ketek_sdd_active = measurement_mode.create_group(
|
||||
name="Single_Element_Partial_Fluorescence_Yield"
|
||||
)
|
||||
se_sdd = ketek_sdd_active.create_dataset(
|
||||
name="Detector", data="Ketex mini single element Silicon Drift Detector"
|
||||
)
|
||||
se_sdd.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<6)) != 0):
|
||||
if (int(ai_chans_bits) & (1 << 6)) != 0:
|
||||
# Create a dataset
|
||||
pips_active = measurement_mode.create_group(name="Total_Flourescence_Yield")
|
||||
tfy = pips_active.create_dataset(name="Detector", data="Mirion Technologies Partially Depeleted PIPS Detector")
|
||||
tfy = pips_active.create_dataset(
|
||||
name="Detector", data="Mirion Technologies Partially Depeleted PIPS Detector"
|
||||
)
|
||||
tfy.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<0)) != 0) & ((int(ai_chans_bits) & (1<<2)) != 0):
|
||||
if ((int(ai_chans_bits) & (1 << 0)) != 0) & ((int(ai_chans_bits) & (1 << 2)) != 0):
|
||||
# Create a dataset
|
||||
ai0ai2_active = measurement_mode.create_group(name="Sample_Transmission")
|
||||
sam_trans = ai0ai2_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
||||
sam_trans = ai0ai2_active.create_dataset(
|
||||
name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers"
|
||||
)
|
||||
sam_trans.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<2)) != 0) & ((int(ai_chans_bits) & (1<<4)) != 0):
|
||||
if ((int(ai_chans_bits) & (1 << 2)) != 0) & ((int(ai_chans_bits) & (1 << 4)) != 0):
|
||||
# Create a dataset
|
||||
ai2ai4_active = measurement_mode.create_group(name="Reference_Transmission")
|
||||
ref_trans = ai2ai4_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
||||
ref_trans = ai2ai4_active.create_dataset(
|
||||
name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers"
|
||||
)
|
||||
ref_trans.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
main_data = entry.create_group(name="data")
|
||||
@@ -267,45 +289,57 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
##################
|
||||
## energy, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
|
||||
energy = main_data.create_group(name="energy")
|
||||
energy.attrs["NX_class"] = "NXdata"
|
||||
energy.attrs["units"] = "eV"
|
||||
|
||||
main_data.create_soft_link(name="energy", target="/entry/collection/readout_groups/async/nidaq/nidaq_energy/value")
|
||||
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="energy",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_energy/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## i0
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<0)) !=0:
|
||||
|
||||
if (int(ai_chans_bits) & (1 << 0)) != 0:
|
||||
i0 = main_data.create_group(name="i0")
|
||||
i0.attrs["NX_class"] = "NXdata"
|
||||
i0.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i0", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai0_mean/value")
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="i0",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_ai0_mean/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## i1
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<2)) !=0:
|
||||
|
||||
if (int(ai_chans_bits) & (1 << 2)) != 0:
|
||||
i1 = main_data.create_group(name="i1")
|
||||
i1.attrs["NX_class"] = "NXdata"
|
||||
i1.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i1", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai2_mean/value")
|
||||
main_data.create_soft_link(
|
||||
name="i1",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_ai2_mean/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## i2
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<4)) !=0:
|
||||
if (int(ai_chans_bits) & (1 << 4)) != 0:
|
||||
i2 = main_data.create_group(name="i2")
|
||||
i2.attrs["NX_class"] = "NXdata"
|
||||
i2.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i2", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai4_mean/value")
|
||||
main_data.create_soft_link(
|
||||
name="i2",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_ai4_mean/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## ci sum
|
||||
@@ -316,38 +350,46 @@ class DebyeNexusStructure(DefaultFormat):
|
||||
ci_sum.attrs["NX_class"] = "NXdata"
|
||||
ci_sum.attrs["units"] = "counts"
|
||||
|
||||
main_data.create_soft_link(name="Fluorescence_Sum", target="/entry/collection/readout_groups/async/nidaq/nidaq_cisum/value")
|
||||
main_data.create_soft_link(
|
||||
name="Fluorescence_Sum",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_cisum/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## mu sample, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<0)) !=0:
|
||||
if (int(add_chans_bits) & (1 << 0)) != 0:
|
||||
mu_sample = main_data.create_group(name="mu_sample")
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="mu_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_abs/value")
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="mu_sample",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_abs/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## fluo sample, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<1)) !=0:
|
||||
if (int(add_chans_bits) & (1 << 1)) != 0:
|
||||
mu_sample = main_data.create_group(name="fluo_sample")
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="fluo_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_fluo/value")
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="fluo_sample",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_fluo/value",
|
||||
)
|
||||
|
||||
##################
|
||||
## mu reference, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<2)) !=0:
|
||||
|
||||
if (int(add_chans_bits) & (1 << 2)) != 0:
|
||||
mu_reference = main_data.create_group(name="mu_reference")
|
||||
mu_reference.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="mu_reference", target="/entry/collection/readout_groups/async/nidaq/nidaq_ref_abs/value")
|
||||
|
||||
|
||||
|
||||
|
||||
main_data.create_soft_link(
|
||||
name="mu_reference",
|
||||
target="/entry/collection/readout_groups/async/nidaq/nidaq_ref_abs/value",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user