Files
EsfRixsApps/furkaRIXS.py
2022-11-03 09:14:49 +01:00

500 lines
19 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# *-----------------------------------------------------------------------*
# | |
# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
# | Based on Zac great first implementation |
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
# *-----------------------------------------------------------------------*
"""
SwissMx experiment application.
Based on Zac great first implementation
bitmask for simulation:
0x01: backlight
0x02: illumination
0x04: zoom
0x08: camera
0x10: Deltatau motors
0x20: SmarAct motors
"""
import logging
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
logging.getLogger('PyQt5.uic').setLevel(logging.INFO)
#logging.getLogger('requests').setLevel(logging.INFO)
#logging.getLogger('urllib3').setLevel(logging.INFO)
#logging.getLogger('paramiko').setLevel(logging.INFO)
logging.getLogger('matplotlib').setLevel(logging.INFO)
#logging.getLogger('PIL').setLevel(logging.INFO)
#logging.getLogger('illumination').setLevel(logging.INFO)
#logging.getLogger('zoom').setLevel(logging.INFO)
#logging.getLogger('pbtools.misc.pp_comm').setLevel(logging.INFO)
_log = logging.getLogger("furkaRIXS")
import time
class timestamp():
def __init__(self):
self.t=time.time()
def log(self,txt):
t=time.time()
print(txt+f'{t-self.t:6.3g}')
self.t=t
ts=timestamp()
ts.log('Import part 1/8:')
import sys, os, time, signal
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use('Qt5Agg') # needed to avoid blocking of ui !
ts.log('Import part 2/8:')
ts.log('Import part 3/8:')
import qtawesome
from PyQt5 import QtCore, QtGui,QtWidgets
from PyQt5.uic import loadUiType
from PyQt5.QtCore import Qt, pyqtSlot, QSize, QRegExp, pyqtSignal, QObject, QThread, QRectF,QT_VERSION_STR
from PyQt5.QtGui import QKeySequence, QPixmap, QRegExpValidator, QFont
from PyQt5.QtWidgets import (
QAction, QApplication, QDoubleSpinBox, QFileDialog, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QMessageBox, QPlainTextEdit, QProgressBar, QProgressDialog, QPushButton, QShortcut, QSizePolicy, QSpinBox,
QSplashScreen, QTextBrowser, QToolBox, QVBoxLayout, QWidget,)
ts.log('Import part 4/8:')
from epics_widgets.MotorTweak import MotorTweak
from epics_widgets.SmaractMotorTweak import SmaractMotorTweak
from epics_widgets.SimMotorTweak import SimMotorTweak
ts.log('Import part 5/8:')
import numpy as np
np.set_printoptions(suppress=True,linewidth=196)
import pyqtgraph as pg
pg.setConfigOptions(antialias=True,imageAxisOrder='row-major')
ts.log('Import part 6/8:')
import epics
from epics.ca import pend_event
def get_version(path='.'):
#sys.stdout.write('getVersion() -> using git command -> ')
p = subprocess.Popen(f'git -C {path} describe --match ''*.*.*'' --long --tags --dirty', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
retval = p.wait()
res=p.stdout.readline()
p.stdout.close()
#res=res.decode()[1:-1].split('-',2)
res=res.decode()[:-1].split('-',2)
ver='.'.join(res[:2])
gitcmt=res[2][1:]
return (ver,gitcmt)
def sigint_handler(*args):
"""Handler for the SIGINT signal."""
app=QApplication.instance()
app.quit()
def add_widgets_to_toolbox(toolbox, label, widget_list=[]):
block = QWidget()
block.setAccessibleName(label)
block.setContentsMargins(0, 0, 0, 0)
block.setLayout(QVBoxLayout())
for w in widget_list:
block.layout().addWidget(w)
block.layout().addStretch()
item = toolbox.addItem(block, label)
return item
class StartupSplash:
def __init__(self):
splash_pix = QPixmap("logo/256x256.png")
self._wnd=splash = QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint)
splash.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
splash.setEnabled(False)
self._prgsBar=prgs=QProgressBar(splash)
prgs.setMaximum(100)
prgs.setGeometry(0, splash_pix.height() - 50, splash_pix.width(), 20)
splash.show()
def set(self,i,msg):
self._prgsBar.setValue(i)
self._wnd.showMessage(f"<font color='red'>{msg}</font></h1>", int(Qt.AlignBottom|Qt.AlignCenter), Qt.black)
app=QApplication.instance()
app.processEvents()
time.sleep(.1)
Ui_MainWindow, QMainWindow = loadUiType("furkaRIXS.ui")
class WndFurkaRIXS(QMainWindow, Ui_MainWindow):
def __init__(self,):
super(WndFurkaRIXS, self).__init__()
self.setupUi(self)
app=QApplication.instance()
title = "Furka RIXS - Furka Resonant inelastic X-ray scattering spectrodscopy"
self.setWindowTitle(title)
self._tweakers = {}
#self.init_settings()
self.setup_sliders()
self.init_graphics()
self.init_actions()
def setup_sliders(self):
cont = self._wdVert2
self._tweak_container = QWidget()
self._tweak_container.setLayout(QVBoxLayout())
cont.layout().addWidget(self._tweak_container)
layout = self._tweak_container.layout()
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
toolbox = QToolBox()
layout.addWidget(toolbox)
self.build_group_rawmotors(toolbox)
#self.build_group_faststage(toolbox)
# self.build_slits_group(toolbox)
#self.build_group_collimator(toolbox)
#self.build_group_posttube(toolbox)
#self.build_group_xeye(toolbox)
#self.build_cryo_group(toolbox)
# monitor all axis for an axis fault
#for key, tweaker in self.tweakers.items():
# tweaker.event_axis_fault.connect(self.cb_axis_fault)
def init_graphics(self):
app = QApplication.instance()
#cfg = app._cfg
#geo = app._geometry
self.glw = pg.GraphicsLayoutWidget()
self._wdVert1.setLayout(QVBoxLayout())
self._wdVert1.layout().addWidget(self.glw)
self.glw.show()
self.glw.scene().sigMouseMoved.connect(self.cb_mouse_move)
self.glw.scene().sigMouseClicked.connect(self.cb_mouse_click)
#--- viewbox ---
self.vb=vb=self.glw.addViewBox(invertY=False,border='r',enableMenu=True)
#TODO: vb.enableAutoRange(enable=True), vb.autoRange() does not work for ItemGroups
#therefore set the vieweRange manually
pad=10
vb.setRange(QRectF(-1200-pad,-1000-pad,1200+2*pad,1000+2*pad))
vb.setAspectLocked(True)
vb.setBackgroundColor((120, 90, 90))
tr=QtGui.QTransform() # prepare ImageItem transformation:
# opt_ctr=app._geometry._opt_ctr
# #--- image group ---
# # uses image transformation
# # contains image and opticalcenter
# self._goImgGrp=grp=pg.ItemGroup()
# self.vb.addItem(grp)
# trf=cfg.value(AppCfg.GEO_CAM_TRF)
# # be aware: setTransform is transposed!
# # Qt uses: p'=(p.T*A.T).T , but I am used: p'=A*p, with p=[x,y,1].T
# tr.setMatrix(trf[0,0],trf[1,0],trf[2,0], #(-1, 0, 0,
# trf[0,1],trf[1,1],trf[2,1], # 0,-1, 0,
# trf[0,2],trf[1,2],trf[2,2]) # 0, 0, 1)
# grp.setTransform(tr) # assign transform
# #--- image ---
# self._goImg=img=pg.ImageItem() #border=pg.mkPen('r',width=2))
# grp.addItem(img)
# #--- opctical center ----
# oc_sz=np.array((50,50))
# #self._goOptCtr=obj=UsrGO.Marker(-opt_ctr+oc_sz/2, oc_sz,mode=1)
# self._goOptCtr=obj=UsrGO.Marker(opt_ctr-oc_sz/2, oc_sz,mode=1, movable=False)
# obj.sigRegionChangeFinished.connect(self.cb_marker_moved)
# grp.addItem(obj)
#--- grid ---
try:
self._goGrid=grid=pg.GridItem(pen=(0,255,0),textPen=(0,255,0)) #green grid and labels
except NameError:
_log.debug('workaround for typo in pyqtgraph:0.11.0')
from PyQt5.QtGui import QPen,QColor
self._goGrid=grid=pg.GridItem() # green grid and labels
grid.opts['pen']=QPen(QColor(0, 255, 0))
grid.opts['textPen']=QPen(QColor(0, 255, 0))
#tr.reset()
#grid.setTransform(tr) # assign transform
vb.addItem(grid)
# #--- fixed group ---
# # uses pix2pos transformation with a fixed fx,fy value =(0,0)
# # contains beam marker
# self._goFixGrp=grp=pg.ItemGroup()
# geo.interp_zoom(1)
# pix2pos=geo._pix2pos
# A=np.asarray(pix2pos.I)
# tr=grp.transform()
# p1=np.hstack((opt_ctr,1)) #position of optical center on image item
# p2=np.matmul(trf, p1) #position of optical center on viewbox
# tr.setMatrix(A[0,0], A[0,1], 0,
# A[1,0], A[1,1], 0,
# p2[0], p2[1], 1) # translate dx,dy
# grp.setTransform(tr)
# self.vb.addItem(grp)
# #--- beam marker ---
# size_eu=cfg.value(AppCfg.GEO_BEAM_SZ)/1000 # convert from um to mm
# pos_eu=cfg.value(AppCfg.GEO_BEAM_POS)/1000 # convert from um to mm
# self._goBeamMarker=obj=UsrGO.Marker(pos_eu-size_eu/2,size_eu,mode=0,movable=False)
# obj.sigRegionChangeFinished.connect(self.cb_marker_moved)
# #bm.setTransform(tr) # assign transform
# grp.addItem(obj)
def init_actions(self):
app = QApplication.instance()
self._actQuit.triggered.connect(self.cb_really_quit)
self._actPreferences.triggered.connect(self.cb_modify_app_param)
self._actAbout.triggered.connect(self.cb_about)
def build_group_rawmotors(self, toolbox):
# dev | Abbr | Motion Axis | Unit |
#-------------------------------------------------------------------------------------------------------
# GCR.1 | MT | Mask translation | Grating Chamber |
# GCR.2 | GTZ | Grating translation along Z-axis (Grating along beam) | Grating Chamber |
# GCR.3 | GTY1 | Grating translation along Y-axis (Grating height) Wedge leveller 1 | Grating Chamber |
# GCR.4 | GTY2 | Grating translation along Y-axis (Grating height) Wedge leveller 2 | Grating Chamber |
# GCR.5 | GRX | Grating rotation around X-axis (pitch) | Grating Chamber |
# GCR.6 | GTX | Grating translation along X-axis (Grating exchange) | Grating Chamber |
#-------------------------------------------------------------------------------------------------------
# DUR.1 | ABT | Aperture Blade Top | Diagnostics unit |
# DUR.2 | ABB | Aperture Blade Bottom | Diagnostics unit |
# DUR.3 | ABL | Aperture Blade Left | Diagnostics unit |
# DUR.4 | ABR | Aperture Blade Right | Diagnostics unit |
# DUR.5 | FT | Filter and Screen translation | Diagnostics unit |
#-------------------------------------------------------------------------------------------------------
# DCR.1 | DTZ | Detector Translation along Z-axis (R2 horizontal) | Detector Chamber |
# DCR.2 | DTY1 | Detector Translation along Y-axis (R2 vert.) | Detector Chamber |
# DCR.3 | DTY2 | Detector Translation along Y-axis (R2 vert.) | Detector Chamber |
# DCR.4 | DRX | Detector Rotation around X-axis (ɣ) | Detector Chamber |
# DCR.5 | RDC1 | RIXS Deformation Compensation (wedge leveller 1) | Spectrometer |
# DCR.6 | RDC2 | RIXS Deformation Compensation (wedge leveller 2) | Spectrometer |
# DCR.7 | RRY1 | RIXS Rotation around Y-axis (2θ) | Spectrometer |
# DCR.8 | RRY2 | RIXS Rotation around Y-axis (2θ) | Spectrometer |
# pfx=QApplication.instance()._cfg.value(AppCfg.GBL_DEV_PREFIX)[1]
pfx='SATES30-RX:MOT_'
#SATES30-RIXS:MOT_2TRY.
widgets=[
self.create_tweaker(f"{pfx}MT", alias="gc_mt", label="MT", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}GTZ", alias="gc_gtz", label="GTZ", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}GTY1",alias="gc_gty1",label="GTY1",mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}GTY2",alias="gc_gty2",label="GTY2",mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}GRX", alias="gc_grx", label="GRX", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}GTX", alias="gc_gtx", label="GTX", mtype=SimMotorTweak),
]
add_widgets_to_toolbox(toolbox,"Grating Chamber",widget_list=widgets)
widgets=[
self.create_tweaker(f"{pfx}ABT", alias="gc_mt", label="MT", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}ABB", alias="gc_gtz", label="GTZ", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}ABL",alias="gc_gty1",label="GTY1",mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}ABR",alias="gc_gty2",label="GTY2",mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}FT", alias="gc_grx", label="GRX", mtype=SimMotorTweak),
]
add_widgets_to_toolbox(toolbox,"Diagnostics unit",widget_list=widgets)
widgets=[
self.create_tweaker(f"{pfx}DTZ", alias="gc_mt", label="MT", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}DTY1", alias="gc_gtz", label="GTZ", mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}DTY2",alias="gc_gty1",label="GTY1",mtype=SimMotorTweak),
self.create_tweaker(f"{pfx}DRX",alias="gc_gty2",label="GTY2",mtype=SimMotorTweak),
]
add_widgets_to_toolbox(toolbox,"Detector Chamber",widget_list=widgets)
def create_tweaker(self, rec, alias=None, label=None, mtype=SimMotorTweak, **kwargs):
m=mtype()
m.connect_motor(rec, label, **kwargs)
self._tweakers[alias] = m
return m
def cb_really_quit(self):
"""called when user Ctrl-Q the app"""
if QMessageBox.question(self, "", "Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) == QMessageBox.Yes:
self._do_quit = True
self.close()
def cb_modify_app_param(self):
wnd=WndParameter(self)
wnd.show()
def cb_about(self):
try:
ver,gitcmt=get_version()
v_txt=f'{ver} git:{gitcmt}'
except:
v_txt='git version failed'
txt=f'''About Swissmx:
FurkaRIXS: {v_txt}
qt:{QT_VERSION_STR}
pyqtgraph:{pg.__version__}
numpy:{np.__version__}
matplotlib:{mpl.__version__}
epics:{epics.__version__}
Copyright (c) 2022 by Paul Scherrer Institute
(http://www.psi.ch)
Author Thierry Zamofing (thierry.zamofing@psi.ch)
'''
QMessageBox.about(self, "FurkaRIXS", txt)
pass
def cb_mouse_move(self, pos):
app = QApplication.instance()
return
geo = app._geometry
#pos = pixel position on the widget
task = self.active_task()
z = app._zoom.get_val()
bm=self._goBeamMarker
#pos=event.scenePos()
pImg=pg.Point(self._goImg.mapFromScene(pos))
pTrk=pg.Point(self._goTracked.mapFromScene(pos))
pFix=pg.Point(self._goFixGrp.mapFromScene(pos))
pFix-=bm.pos()+bm.size()/2
fx=self.tweakers["fast_x"].get_val()
fy=self.tweakers["fast_y"].get_val()
pRel=pTrk-(fx,fy)
#s=f'pImg{pImg} pTrk{pTrk} bm._pos_eu{bm._pos_eu}'
try:
pln=geo._fitPlane
except AttributeError:
cz=np.nan
else:
cz=pln[0]*pTrk[0]+pln[1]*pTrk[1]+pln[2] # z=ax+by+c
s=\
f'img pix ({pImg[0]:0.1f} {pImg[1]:0.1f})px \u23A2 ' \
f'stage ({pTrk[0]:0.4f} {pTrk[1]:>0.4f} {cz:>0.4f})mm \u23A2 ' \
f'dist to beam ({pFix[0]:>0.4f} {pFix[1]:>0.4f})mm '
#f'dist to beam ({pRel[0]:>0.6g} {pRel[1]:>0.6g}mm) '
#_log.debug(s)
self._lb_coords.setText(s)
def cb_mouse_click(self, event):
#_log.debug("{}".format(event))
#_log.debug("screen pos {}".format(event.screenPos())) #pixel position on the whole screen
#_log.debug("scene pos {}".format(event.scenePos())) #pixel position on the scene (including black frame)
#_log.debug(" pos {}".format(event.pos())) #pixel position of the ckicked object mapped to its coordinates
#p=event.scenePos()
#_log.debug(f"vb pos {self.vb.mapFromScene(p)}") #pixel position on the scene (including black frame)
#for o in self.vb.childGroup.childItems():
# _log.debug(f"{type(o)} pos {o.mapFromScene(p)}") #pixel position on the scene (including black frame)
#_log.debug(f"currentItem:{event.currentItem}")
app=QApplication.instance()
mod=event.modifiers()
btn=event.button()
#dblclick = event.double()
#if btn==Qt.LeftButton:
# if mod&Qt.ShiftModifier:
# pass
# elif mod&Qt.ControlModifier:
# pos=event.scenePos()
# _log.debug(f'move to position :scene pos {pos}')
if __name__=="__main__":
def main():
#_log.info(f'Version: pyqtgraph:{pg.__version__} matplotlib:{mpl.__version__} numpy:{np.__version__} epics:{epics.__version__} qt:{QT_VERSION_STR}' )
_log.info(f'Version: pyqtgraph:{pg.__version__} epics:{epics.__version__} qt:{QT_VERSION_STR}' )
import argparse, socket
hostname=socket.gethostname()
if hostname=='ganymede':
#use EPICS locally
#os.environ['EPICS_CA_ADDR_LIST']='localhost'
#use EPICS if connected to ESC network
os.environ['EPICS_CA_ADDR_LIST'] ='129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066'
epilog=__doc__ #+'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n'
parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0)
args = parser.parse_args()
_log.info('Arguments:{}'.format(args.__dict__))
from PyQt5.QtWidgets import QApplication
# set app icon
app = QApplication(sys.argv)
app_icon = QtGui.QIcon()
#app_icon.addFile("artwork/logo/16x16.png", QtCore.QSize(16, 16))
#app_icon.addFile("artwork/logo/24x24.png", QtCore.QSize(24, 24))
#app_icon.addFile("artwork/logo/32x32.png", QtCore.QSize(32, 32))
#app_icon.addFile("artwork/logo/48x48.png", QtCore.QSize(48, 48))
app_icon.addFile("logo/256x256.png", QtCore.QSize(256, 256))
app.setWindowIcon(app_icon)
startupWin=StartupSplash()
app._args=args
startupWin.set(5, f'load settings')
startupWin.set(10, f'load settings')
#app._cfg=cfg=AppCfg()
#app._geometry=geometry.geometry()
#app._geometry._lut_pix2pos=cfg.value(cfg.GEO_PIX2POS)
#app._geometry._opt_ctr=cfg.value(cfg.GEO_OPT_CTR)
#startupWin.set(15, f'connect backlight')
#pfx=app._cfg.value(AppCfg.GBL_DEV_PREFIX)[0]
#dft_pos_bklgt=app._cfg.value(AppCfg.DFT_POS_BKLGT)
#if args.sim&0x01:
# app._backlight = backlight.Backlight(None,dft_pos_bklgt)
#else:
# app._backlight = backlight.Backlight(f'{pfx}:MOT_BLGT',dft_pos_bklgt)
#startupWin.set(20, f'connect illumination')
#if args.sim&0x02:
# app._illumination = illumination.IlluminationControl(None)
#else:
# app._illumination = illumination.IlluminationControl()
#startupWin.set(40, f'connect zoom')
#if args.sim&0x04:
# app._zoom = zoom.QopticZoom(None)
#else:
# app._zoom = zoom.QopticZoom()
#startupWin.set(60, f'connect camera')
#if args.sim&0x08:
# app._camera = camera.epics_cam(None)
# app._camera.sim_gen(mode=1)
#else:
# app._camera = camera.epics_cam()
##app._camera.run() is called in center_piece_update
startupWin.set(60, f'start main window')
app._mainWnd=wnd=WndFurkaRIXS()
wnd.show()
startupWin._wnd.finish(wnd)
# needed so pycharm can restart application
signal.signal(signal.SIGINT, sigint_handler)
#main.update_user_and_storage() #ZAC: orig. code
sys.exit(app.exec_())
main()