From 6eff498fbace9a4ff1addb0de6b6b21d424d497d Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Mon, 31 Oct 2022 14:24:55 +0100 Subject: [PATCH] add optics sample --- furkaRIXS.py | 239 ++++++++++++++++++++++++++++++++++++++++++++--- furkaRIXS.ui | 10 +- logo/256x256.png | Bin 0 -> 26346 bytes opticsExample.py | 180 +++++++++++++++++++++++++++++++++++ 4 files changed, 408 insertions(+), 21 deletions(-) create mode 100644 logo/256x256.png create mode 100755 opticsExample.py diff --git a/furkaRIXS.py b/furkaRIXS.py index 3c40da9..c42393b 100755 --- a/furkaRIXS.py +++ b/furkaRIXS.py @@ -22,6 +22,15 @@ bitmask for simulation: 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 @@ -34,7 +43,7 @@ class timestamp(): self.t=t ts=timestamp() ts.log('Import part 1/8:') -import sys, os, time +import sys, os, time, signal import matplotlib as mpl import matplotlib.pyplot as plt mpl.use('Qt5Agg') # needed to avoid blocking of ui ! @@ -79,11 +88,32 @@ def sigint_handler(*args): 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"{msg}", 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(WndSwissMx, self).__init__() + super(WndFurkaRIXS, self).__init__() self.setupUi(self) app=QApplication.instance() @@ -93,9 +123,193 @@ class WndFurkaRIXS(QMainWindow, Ui_MainWindow): #self.init_settings() #self.setup_sliders() - #self.init_graphics() - #self.init_actions() + 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__": @@ -118,20 +332,21 @@ if __name__=="__main__": from PyQt5.QtWidgets import QApplication # set app icon - #app = QApplication(sys.argv) - #app_icon = QtGui.QIcon() + 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("artwork/logo/256x256.png", QtCore.QSize(256, 256)) - #app.setWindowIcon(app_icon) + app_icon.addFile("logo/256x256.png", QtCore.QSize(256, 256)) + app.setWindowIcon(app_icon) - #startupWin=StartupSplash() + startupWin=StartupSplash() app._args=args - #startupWin.set(5, f'load settings') + startupWin.set(5, f'load settings') + startupWin.set(10, f'load settings') #app._cfg=cfg=AppCfg() #app._geometry=geometry.geometry() @@ -165,11 +380,11 @@ if __name__=="__main__": # app._camera = camera.epics_cam() ##app._camera.run() is called in center_piece_update - #startupWin.set(60, f'start main window') + startupWin.set(60, f'start main window') app._mainWnd=wnd=WndFurkaRIXS() wnd.show() - #startupWin._wnd.finish(wnd) + startupWin._wnd.finish(wnd) # needed so pycharm can restart application signal.signal(signal.SIGINT, sigint_handler) diff --git a/furkaRIXS.ui b/furkaRIXS.ui index 15d3369..62b6196 100644 --- a/furkaRIXS.ui +++ b/furkaRIXS.ui @@ -47,15 +47,7 @@ - - - - - PushButton - - - - + diff --git a/logo/256x256.png b/logo/256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e84f7dc8d0b3b9ba6c7e705ad735cce83cd5cf GIT binary patch literal 26346 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 literal 0 HcmV?d00001 diff --git a/opticsExample.py b/opticsExample.py new file mode 100755 index 0000000..036c6e6 --- /dev/null +++ b/opticsExample.py @@ -0,0 +1,180 @@ +#!/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) | +# *-----------------------------------------------------------------------* +# -*- coding: utf-8 -*- +""" +Optical system design demo + + + +""" + +#import initExample ## Add path to library (just for examples; you do not need this) + + +#sys.path.insert(0, "/home/myname/pythonfiles") + +from pyqtgraph.examples.optics import * + +import pyqtgraph as pg + +import numpy as np +from pyqtgraph import Point + +app = pg.QtGui.QApplication([]) + +w = pg.GraphicsLayoutWidget(show=True, border=0.5) +w.resize(1000, 900) +w.show() + + + +### Curved mirror demo + +view = w.addViewBox() +view.setAspectLocked() +#grid = pg.GridItem() +#view.addItem(grid) +view.setRange(pg.QtCore.QRectF(-50, -30, 100, 100)) + +optics = [] +rays = [] +m1 = Mirror(r1=-100, pos=(5,0), d=5, angle=-15) +optics.append(m1) +m2 = Mirror(r1=-70, pos=(-40, 30), d=6, angle=180-15) +optics.append(m2) + +allRays = [] +for y in np.linspace(-10, 10, 21): + r = Ray(start=Point(-100, y)) + view.addItem(r) + allRays.append(r) + +for o in optics: + view.addItem(o) + +t1 = Tracer(allRays, optics) + + + +### Dispersion demo + +optics = [] + +view = w.addViewBox() + +view.setAspectLocked() +#grid = pg.GridItem() +#view.addItem(grid) +view.setRange(pg.QtCore.QRectF(-10, -50, 90, 60)) + +optics = [] +rays = [] +l1 = Lens(r1=20, r2=20, d=10, angle=8, glass='Corning7980') +optics.append(l1) + +allRays = [] +for wl in np.linspace(355,1040, 25): + for y in [10]: + r = Ray(start=Point(-100, y), wl=wl) + view.addItem(r) + allRays.append(r) + +for o in optics: + view.addItem(o) + +t2 = Tracer(allRays, optics) + + + +### Scanning laser microscopy demo + +w.nextRow() +view = w.addViewBox(colspan=2) + +optics = [] + + +#view.setAspectLocked() +view.setRange(QtCore.QRectF(200, -50, 500, 200)) + + + +## Scan mirrors +scanx = 250 +scany = 20 +m1 = Mirror(dia=4.2, d=0.001, pos=(scanx, 0), angle=315) +m2 = Mirror(dia=8.4, d=0.001, pos=(scanx, scany), angle=135) + +## Scan lenses +l3 = Lens(r1=23.0, r2=0, d=5.8, pos=(scanx+50, scany), glass='Corning7980') ## 50mm UVFS (LA4148) +l4 = Lens(r1=0, r2=69.0, d=3.2, pos=(scanx+250, scany), glass='Corning7980') ## 150mm UVFS (LA4874) + +## Objective +obj = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, scany), glass='Corning7980') + +IROptics = [m1, m2, l3, l4, obj] + + + +## Scan mirrors +scanx = 250 +scany = 30 +m1a = Mirror(dia=4.2, d=0.001, pos=(scanx, 2*scany), angle=315) +m2a = Mirror(dia=8.4, d=0.001, pos=(scanx, 3*scany), angle=135) + +## Scan lenses +l3a = Lens(r1=46, r2=0, d=3.8, pos=(scanx+50, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380) +l4a = Lens(r1=0, r2=46, d=3.8, pos=(scanx+250, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380) + +## Objective +obja = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, 3*scany), glass='Corning7980') + +IROptics2 = [m1a, m2a, l3a, l4a, obja] + + + +for o in set(IROptics+IROptics2): + view.addItem(o) + +IRRays = [] +IRRays2 = [] + +for dy in [-0.4, -0.15, 0, 0.15, 0.4]: + IRRays.append(Ray(start=Point(-50, dy), dir=(1, 0), wl=780)) + IRRays2.append(Ray(start=Point(-50, dy+2*scany), dir=(1, 0), wl=780)) + +for r in set(IRRays+IRRays2): + view.addItem(r) + +IRTracer = Tracer(IRRays, IROptics) +IRTracer2 = Tracer(IRRays2, IROptics2) + +phase = 0.0 +def update(): + global phase + if phase % (8*np.pi) > 4*np.pi: + m1['angle'] = 315 + 1.5*np.sin(phase) + m1a['angle'] = 315 + 1.5*np.sin(phase) + else: + m2['angle'] = 135 + 1.5*np.sin(phase) + m2a['angle'] = 135 + 1.5*np.sin(phase) + phase += 0.2 + +timer = QtCore.QTimer() +timer.timeout.connect(update) +timer.start(40) + + + + + +## Start Qt event loop unless running in interactive mode or using pyside. +if __name__ == '__main__': + import sys + if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): + QtGui.QApplication.instance().exec_()