395 lines
14 KiB
Python
Executable File
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()
|