Files
SwissMX/app_config.py

408 lines
15 KiB
Python

#TODO:
# currently 2 settings/configs are used
# QSettings -> cat ~/.config/Paul\ Scherrer\ Institut/SwissMX.conf
# yaml -> swissmx.yaml
# QSettings are changed by program
# #yaml is fixed and not altened by program
import logging
_log = logging.getLogger(__name__)
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QApplication, QLineEdit
import json
import numpy as np
import GenericDialog
class MyJsonEncoder(json.JSONEncoder):
""" Special json encoder for numpy types """
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
elif type(obj) not in (dict,list,str,int):
_log.error('dont know how to json')
return repr(obj)
return json.JSONEncoder.default(self, obj)
class AppCfg(QSettings):
GBL_FLD_SCR_SHOT="global/folder_screenshot"
GEO_OPT_CTR='geometry/opt_ctr'
GEO_PIX2POS='geometry/pix2pos'
GEO_BEAM_SZ="geometry/beam_size"
GEO_BEAM_POS="geometry/beam_pos"
GEO_CAM_PARAM="geometry/cam_param"
WINDOW_GEOMETRY="window/geometry"
WINDOW_SPLITTER="window/splitter"
WINDOW_STATE= "window/state"
PST_X_UP ="post_sample_tube/x_up"
PST_Y_UP ="post_sample_tube/y_up"
PST_X_DOWN="post_sample_tube/x_down"
PST_Y_DOWN="post_sample_tube/y_down"
PST_DX ="post_sample_tube/dx"
PST_DY ="post_sample_tube/dy"
PST_TZ_IN ="post_sample_tube/z_in"
PST_TZ_OUT="post_sample_tube/z_out"
COL_DX ="collimator/dx"
COL_DY ="collimator/dy"
COL_X_IN ="collimator/x_in"
COL_Y_IN ="collimator/y_in"
BKLGT_IN ="backlight/in"
BKLGT_OUT ="backlight/out"
DT_HOST="deltatau/host"
DT_SHOW_PLOTS="deltatau/show_plots"
DT_VEL_SCL="deltatau/velocity_scale"
# ---------- OBSOLETE ??? ----------
#ZOOM_BUTTONS="sample_viewing/zoom_buttons"
SKIP_ESCAPE_TRANSITIONS_IF_SAFE="escape/skip_transitions_if_safe"
CRYOJET_MOTION_ENABLED="cryojet/motion_enabled"
CRYOJET_NOZZLE_OUT="cryojet/nozzle_out"
CRYOJET_NOZZLE_IN="cryojet/nozzle_in"
#DELTATAU_HOST="deltatau/host"
#DELTATAU_SHOW_PLOTS="deltatau/show_plots"
DELTATAU_OMEGACOS="deltatau/omegacos"
DELTATAU_SORT_POINTS="deltatau/sort_points"
#DELTATAU_VELOCITY_SCALE="deltatau/velocity_scale"
CAMERA_TRANSFORMATIONS="camera/transformations"
CAMERA_ZOOM_TO_PPM="camera/zoom_to_ppm"
EXPERIMENT_PGROUP="experiment/pgroup"
EXPERIMENT_UID="experiment/uid"
ACTIVATE_PULSE_PICKER="scanning/activate_pulse_picker"
def __init__(self):
super(AppCfg, self).__init__("PSI", "SwissMX")
keys = self.allKeys()
# Dump config to debug
#for k in keys:
# print(k, self.value(k))
#set default keys if not existing
if AppCfg.GEO_BEAM_SZ not in keys:
_log.warning(f'{AppCfg.GEO_BEAM_SZ} not defined. set default')
self.setValue(AppCfg.GEO_BEAM_SZ, [30.2, 25.6]) #([40, 20) -> tuples are not supported natively
if AppCfg.GEO_BEAM_POS not in keys:
_log.warning(f'{AppCfg.GEO_BEAM_POS} not defined. set default')
self.setValue(AppCfg.GEO_BEAM_POS, [23.4, -54.2]) # beam position relativ to optical center in mm
if AppCfg.GEO_PIX2POS not in keys:
_log.warning(f'{AppCfg.GEO_PIX2POS} not defined. use default')
#lut_pix2pos=(np.array([1., 200., 400., 600., 800., 1000.]),
# np.array([[[ 2.42827273e-03, -9.22117396e-05],
# [-1.10489804e-04, -2.42592492e-03]],
# [[ 1.64346103e-03, -7.52341417e-05],
# [-6.60711165e-05, -1.64190224e-03]],
# [[ 9.91307639e-04, -4.02008751e-05],
# [-4.23878232e-05, -9.91563507e-04]],
# [[ 5.98443038e-04, -2.54046255e-05],
# [-2.76831563e-05, -6.02738142e-04]],
# [[ 3.64418977e-04, -1.41389267e-05],
# [-1.55708176e-05, -3.66233567e-04]],
# [[ 2.16526433e-04, -8.23070130e-06],
# [-9.29894004e-06, -2.16842976e-04]]]))
# rough data for binning=1,1
z=np.array((1.0, 200.0, 400.0, 600.0, 800.0, 1000.0))
t1=np.array(([1, 0.0000], [0.0000, -1])).reshape(1, -1)
t2=np.array((0.001214,0.000821,0.000495,0.000299,0.000182,0.000108)).reshape(-1, 1)
trf=(t1*t2).reshape((-1, 2, 2))
lut_pix2pos=(z, trf)
self.setValue(AppCfg.GEO_PIX2POS, lut_pix2pos)
if AppCfg.GEO_OPT_CTR not in keys:
_log.warning(f'{AppCfg.GEO_OPT_CTR} not defined. use default')
opt_ctr=np.array([603.28688025, 520.01112846])
self.setValue(AppCfg.GEO_OPT_CTR, opt_ctr)
#if AppCfg.ACTIVATE_PULSE_PICKER not in keys:
# self.setValue(AppCfg.ACTIVATE_PULSE_PICKER, False)
#if AppCfg.SKIP_ESCAPE_TRANSITIONS_IF_SAFE not in keys:
# self.setValue(AppCfg.SKIP_ESCAPE_TRANSITIONS_IF_SAFE, False)
def sync(self):
super(AppCfg, self).sync()
#import numpy as np
#a=np.array(((1,2,3),(4,5,6)))
#self._yamlFn=fn=os.path.expanduser('~/.config/PSI/SwissMX.yaml')
#_yaml = [{'name': 'John Doe', 'occupation': 'gardener'},
# {'name': 'Lucy Black', 'occupation': 'teacher'}]
#_yaml = {'name': 'John Doe', 'occupation': 'gardener','A':(1,2,3),'B':{1,2,3},'C': {1:(1,2,3),2:'df',3:'dddd'},4:a }
#with open(self._yamlFn, 'w') as f:
# data = yaml.dump(_yaml, f)
# print(data)
def setValue(self, key: str, val): #overload to debug
# only simple lists, str, int, float can not be serialized nicely
t=type(val)
if key in (AppCfg.GEO_PIX2POS,):
val=json.dumps(val, cls=MyJsonEncoder)
elif key in (AppCfg.GEO_CAM_PARAM,):
val=json.dumps(val, cls=MyJsonEncoder)
val=val.replace('"',"'")
elif key in (AppCfg.GEO_OPT_CTR,AppCfg.GEO_BEAM_SZ,AppCfg.GEO_BEAM_POS,):
if type(val)==np.ndarray:
val=val.tolist()
elif type(val)==tuple:
val=list(val)
return super(AppCfg, self).setValue(key,val)
def value(self,key,*vargs,**kwargs): #overload to debug
val=super(AppCfg, self).value(key,*vargs,**kwargs)
if key in (AppCfg.GEO_PIX2POS,):
val=json.loads(val)#, object_hook=MyJsonDecoder)
val=(np.array(val[0]),np.array(val[1]))
if key in (AppCfg.GEO_CAM_PARAM,):
val=val.replace("'",'"')
val=json.loads(val) # , object_hook=MyJsonDecoder)
elif key in (AppCfg.GEO_BEAM_SZ,AppCfg.GEO_BEAM_POS,):
val=np.array(tuple(map(float, val)))/1000
elif key in (AppCfg.GEO_OPT_CTR):
val=np.array(tuple(map(float, val)))
return val
#@property
#def value(self):
# return super(AppCfg, self).value
def option(self,key: str) -> bool:
try:
return self.value(key, type=bool)
except:
_log.error(f"option {key} not known")
return False
def toggle_option(self,key: str):
v = self.value(key, type=bool)
self.setValue(key, not v)
self.sync()
#inst_folder = Path(__file__).absolute().parent
#config_file = inst_folder / "swissmx.yaml"
#configs = yaml.load(config_file.read_text(),Loader=yaml.FullLoader)
#endstation = configs["configure_for"]
#appsconf = configs[endstation]
#simulated = appsconf.get("simulate", False)
#logger.info(f"configuring for endstation: {endstation.upper()}")
#if simulated:
# logger.warning("SIMULATION is ACTIVE")
#css_file = inst_folder / "swissmx.css"
#def font(name: str) -> str:
# p = Path(__file__).absolute().parent / "fonts" / name
# return str(p)
#def logo(size: int = 0) -> str:
# p = Path(__file__).absolute().parent / "logos" / "logo.png"
# if size:
# p = Path(__file__).absolute().parent / "logos" / f"tell_logo_{size}x{size}.png"
# return str(p)
def dlg_geometry(self,obj):
SPN=GenericDialog.Spinner
app=QApplication.instance()
cfg=app._cfg
w, h = map(float,cfg.value(AppCfg.GEO_BEAM_SZ))
d = GenericDialog.GenericDialog(
title="geometry",
message="Enter the size of the beam in microns",
inputs={
"bw": ("beam width um" ,w,SPN(w, min=1, max=200, suffix=" \u00B5m"),),
"bh": ("beam height um",h,SPN(h, min=1, max=200, suffix=" \u00B5m"),),
},
)
if d.exec():
results = d.results
_log.info("Updating beamsize to {}".format(results))
bm_sz= (results["bw"], results["bh"])
_log.debug("types {}".format(type(w)))
cfg.setValue(AppCfg.GEO_BEAM_SZ, bm_sz)
cfg.sync()
bm=obj._goBeamMarker
bm.setSize(bm_sz)
#self._beammark.set_beam_size((w, h))
def dlg_posttube_references(self):
SPN=GenericDialog.Spinner
app=QApplication.instance()
cfg=app._cfg
x_up = cfg.value(AppCfg.PST_X_UP , 0.0,type=float)
y_up = cfg.value(AppCfg.PST_Y_UP , 0.0,type=float)
x_down = cfg.value(AppCfg.PST_X_DOWN, 0.0,type=float)
y_down = cfg.value(AppCfg.PST_Y_DOWN, 0.0,type=float)
dx = cfg.value(AppCfg.PST_DX , 0.0,type=float)
dy = cfg.value(AppCfg.PST_DY , 0.0,type=float)
tz_in = cfg.value(AppCfg.PST_TZ_IN , 0.0,type=float)
tz_out = cfg.value(AppCfg.PST_TZ_OUT, 0.0,type=float)
d = GenericDialog.GenericDialog(
title="Post Sample Tube Configuration",
message="Enter the relative displacements for X and Y to move the post sample tube either in or out.",
inputs={
AppCfg.PST_X_UP : ("Up X" , x_up , SPN(x_up , decimals=3, min=-45.0, max=15.0, suffix=" mm"), ),
AppCfg.PST_Y_UP : ("Up Y" , y_up , SPN(y_up , decimals=3, min=-45.0, max=15.0, suffix=" mm"), ),
AppCfg.PST_X_DOWN: ("Down X" , x_down, SPN(x_down, decimals=3, min=-45.0, max=15.0, suffix=" mm"), ),
AppCfg.PST_Y_DOWN: ("Down Y" , y_down, SPN(y_down, decimals=3, min=-45.0, max=15.0, suffix=" mm"), ),
AppCfg.PST_DX : ("out delta X" , dx , SPN(dx , decimals=3, min=-32.0, max=32.0, suffix=" mm"), ),
AppCfg.PST_DY : ("out delta Y" , dy , SPN(dy , decimals=3, min=-32.0, max=32.0, suffix=" mm"), ),
AppCfg.PST_TZ_IN : ("tube Z in position" , tz_in , SPN(tz_in , decimals=3, min=-8.0 , max=1.0 , suffix=" mm"), ),
AppCfg.PST_TZ_OUT: ("tube Z OUT position", tz_out, SPN(tz_out, decimals=3, min=-8.0 , max=1.0 , suffix=" mm"), ),
},
)
if d.exec():
results = d.results
_log.info("setting post-sample-tube displacements {}".format(results))
for k, v in results.items():
cfg.setValue(k, v)
cfg.sync()
def dlg_collimator_reference_positions(self):
SPN=GenericDialog.Spinner
app=QApplication.instance()
cfg=app._cfg
x_out = cfg.value(AppCfg.COL_DX , 0.0,type=float)
y_out = cfg.value(AppCfg.COL_DY , 0.0,type=float)
x_in = cfg.value(AppCfg.COL_X_IN, 0.0,type=float)
y_in = cfg.value(AppCfg.COL_Y_IN, 0.0,type=float)
d = GenericDialog.GenericDialog(
title="Collimator configuration",
message="Enter reference positions for the collimator",
inputs={
AppCfg.COL_DX: ("Collimator out deltaX", x_out, SPN(x_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"),),
AppCfg.COL_DY: ("Collimator out deltaY", y_out, SPN(y_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"),),
AppCfg.COL_X_IN: ("Collimator in X", x_in, SPN(x_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"),),
AppCfg.COL_Y_IN: ("Collimator in Y", y_in, SPN(y_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"),),
},
)
if d.exec():
results = d.results
_log.info("setting collimator reference positions {}".format(results))
for k, v in results.items():
cfg.setValue(k, v)
cfg.sync()
def dlg_backlight_positions(self):
SPN=GenericDialog.Spinner
app=QApplication.instance()
cfg=app._cfg
p_in = cfg.value(AppCfg.BKLGT_IN,0,type=int)
p_out = cfg.value(AppCfg.BKLGT_OUT,0,type=int)
d = GenericDialog.GenericDialog(
title="Back Light configuration",
message="Enter reference positions for the backlight",
inputs={
AppCfg.BKLGT_IN: ("In position" , p_in , SPN(p_in, min=-30000, max=10), ),
AppCfg.BKLGT_OUT: ("Out position", p_out, SPN(p_out, min=-1000, max=10), ),
},
)
if d.exec():
results = d.results
_log.info("setting back light reference positions {}".format(results))
for k, v in results.items():
cfg.setValue(k, v)
cfg.sync()
def dlg_cryojet_positions(self):
p_in = settings.value(CRYOJET_NOZZLE_IN, type=float)
p_out = settings.value(CRYOJET_NOZZLE_OUT, type=float)
motion_enabled = option(CRYOJET_MOTION_ENABLED)
d = GenericDialog(
title="Cryojet Nozzle Configuration",
message="Enter reference positions for the cryojet nozzle position",
inputs={
CRYOJET_NOZZLE_IN: ("In position", p_in, Spinner(p_in, min=3, max=15)),
CRYOJET_NOZZLE_OUT: ("Out position",p_out,Spinner(p_out, min=-1000, max=10),),
CRYOJET_MOTION_ENABLED: ("Move Cryojet in Transitions",motion_enabled,Checkbox(motion_enabled, "move cryojet"),),
},
)
if d.exec():
results = d.results
_log.info("setting cryojet reference positions {}".format(results))
for k, v in results.items():
settings.setValue(k, v)
settings.sync()
def dlg_deltatau_parameters(self):
SPN=GenericDialog.Spinner
CB=GenericDialog.Checkbox
app=QApplication.instance()
cfg=app._cfg
#dt1 = cfg.value(AppCfg.DT_HOST,'SAR-CPPM-EXPMX1')
dt1 = cfg.value(AppCfg.DT_HOST,'localhost:10001:10002')
dt2 = cfg.value(AppCfg.DT_VEL_SCL, 1, type=float)
dt3 = cfg.option(AppCfg.DT_SHOW_PLOTS)
d = GenericDialog.GenericDialog(
title="Delta Tau Parameters",
message="These parameters affect the data collection.",
inputs={
AppCfg.DT_HOST:("host name (host[:port:port_gather])", dt1, QLineEdit(),),
AppCfg.DT_VEL_SCL: ("Velocity Scale (1=optimal, 0=zero vel at target)", dt2,SPN(dt2, min=0, max=1, suffix=""),),
AppCfg.DT_SHOW_PLOTS: ("show plots after collection", dt3,CB(dt3, "active"),),
#DELTATAU_SORT_POINTS: ("Sort pointshoot/prelocated coords", b,CB(b, "sort points"),),
},
)
if d.exec():
results = d.results
_log.info("setting delta tau parameters {}".format(results))
for k, v in results.items():
cfg.setValue(k, v)
cfg.sync()
def dlg_tell_mount_positions(self):
AUTODRY_ENABLED = "tell/autodry_enabled"
AUTODRY_MAXMOUNTS = "tell/autodry_max_number_of_mounts"
AUTODRY_MAXTIME = "tell/autodry_max_time"
SECS_HOURS = 60 * 60
enabled = option(AUTODRY_ENABLED)
maxtime = settings.value(AUTODRY_MAXTIME, type=float) / SECS_HOURS
maxmounts = settings.value(AUTODRY_MAXMOUNTS, type=int)
d = GenericDialog.GenericDialog(
title="TELL Settings",
message="These control some features of the TELL sample changer",
inputs={
AUTODRY_ENABLED: ("Auto dry", enabled,
Checkbox(enabled, "enabled")),
AUTODRY_MAXMOUNTS: ("Max. num. mounts between dry",maxmounts,
Spinner(maxmounts, decimals=0, min=1, max=100, suffix=" mounts"),),
AUTODRY_MAXTIME: ("Max. time between dry",maxtime,
Spinner(maxtime, decimals=1, min=0.5, max=5, suffix=" hours"),),
},
)
if d.exec():
results = d.results
_log.info("setting tell parameters {}".format(results))
for k, v in results.items():
if k == AUTODRY_MAXTIME:
v = v * SECS_HOURS
settings.setValue(k, v)
settings.sync()