Files
EsfRixsApps/furkaRIXS.py
2022-10-31 14:24:55 +01:00

395 lines
14 KiB
Python
Executable File

#!/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()
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.init_settings()
#self.setup_sliders()
self.init_graphics()
self.init_actions()
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 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()