diff --git a/ARESvis.png b/ARESvis.png new file mode 100644 index 0000000..731b1bd Binary files /dev/null and b/ARESvis.png differ diff --git a/ARESvis/ARESvis.py b/ARESvis/ARESvis.py new file mode 100755 index 0000000..af12257 --- /dev/null +++ b/ARESvis/ARESvis.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# *-----------------------------------------------------------------------* +# | | +# | Copyright (c) 2024 by Paul Scherrer Institute (http://www.psi.ch) | +# | | +# | Author Thierry Zamofing (thierry.zamofing@psi.ch) | +# *-----------------------------------------------------------------------* + +""" +SwissFEL Furka ARES visualization + +bitmask for simulation: + 0x01: EPICS motors + 0x02: + 0x04: + 0x08: + 0x10: + 0x20: + 0x40: + 0x80: + +""" + +import sys, logging +import epics + +_log=logging.getLogger(__name__) + +from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QSlider +from PyQt5.QtGui import QPainter, QColor, QPen +#import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import numpy as np + +class WndVisualizeARES(QWidget): + _lutColor={0:(255,0,0),1:(0,255,0),2:(255,255,0)} + def __init__(self): + super().__init__() + self._param={'ctr':(200, 320), # location of big chamber + 'rCmb':150, # radius of chamber + 'rTrg':10, # radius of target + 'r2Th':100, # radius 2Theta platfform + 'szSeal': (20,50), + 'szArm': (80,250), + 'aaSeal':70, # RIXS sliding angle + 'aaArm':70, # RIXS arm angle + 'airPads':0, # air pads (off=0 on=1 undef=2) + 'defComp':0, # deformation compensation (off=0 on=1 undef=2) + } + # 2thetha angle SATES30-ARES:MOT_2TRY + # detector angle SATES30-ARES:MOT_DRY + # sliding seal SATES30-RIXS:MOT_RY + self.initUI() + + def initUI(self): + self.setGeometry(300, 300, 850, 800) + self.setWindowTitle('ARES visualize') + #label = QLabel('Python', self) + #label.move(50,50) + #b1 = QPushButton("Button1",self) + #b1.move(400,150) + self._wdGrpDraw=w=QGroupBox("ARES chamber",self) + w.move(10,10) + l=QVBoxLayout(w) + + sld={} + for key,rng,tk in (('aaSeal',(0,180),5),('aaArm',(0,180),5),('airPads',(0,2),1),('defComp',(0,2),1),): + sl=QSlider(QtCore.Qt.Horizontal,objectName=key) + sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1]) + sl.setValue(self._param[key]) + sl.setTickPosition(QSlider.TicksBelow);sl.setTickInterval(tk) + l.addWidget(sl) + sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) + sld[key]=sl + + self.show() + + def sldChanged(self,key,val,*args,**kwargs): + p=self._param + #print(key,val) + if key=='aaArm': + p['aaSeal']=v=p['aaSeal']-p['aaArm']+val + wSl=self._wdGrpDraw.findChild(QSlider, 'aaSeal') + wSl.blockSignals(True) + wSl.setValue(int(v)) + wSl.blockSignals(False) + sl=QSlider(QtCore.Qt.Horizontal,objectName=key) + + p[key]=val + self.update() + + def paintEvent(self, e): + p=self._param + ctr=p['ctr'] + aaSeal=p['aaSeal'] + aaArm=p['aaArm'] + rCmb=p['rCmb'] + r2Th=p['r2Th'] + rTrg=p['rTrg'] + xa,ya=p['szArm'] + xs,ys=p['szSeal'] + ap=p['airPads'] + dc=p['defComp'] + + + qp = QPainter() + qp.begin(self) + qp.translate(ctr[0],ctr[1]) + tf0=qp.transform() + qp.setPen(QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)) + qp.setBrush(QColor(155, 155, 155, 128)) + br1=qp.brush() + + + qp.drawEllipse(-rCmb, -rCmb, 2*rCmb, 2*rCmb) # big chamber + qp.drawEllipse(-r2Th, -r2Th, 2*r2Th, 2*r2Th) # big chamber + qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target + qp.drawLine(0,-rCmb-50,0,-rTrg) #beam + qp.rotate(-aaSeal) + qp.translate(0,rCmb) + tf1=qp.transform() + xs2=xs//2 + xa2=xa//2 + ya2=ya//2 + d=int(abs(aaSeal-aaArm)*20) + r=min(155+d,255) + gb=max(155-d,0) + qp.setBrush(QColor(r, gb, gb, 255)) + qp.drawRect(-xs2, 0, xs, ys) # seal bellow + + qp.setBrush(br1) + + qp.setTransform(tf0) + qp.rotate(-aaArm) + qp.translate(0,rCmb+ys) + tf2=qp.transform() + qp.drawRect(-xa2, 0, xa, ya) # girder + #qp.drawEllipse(-ya2, 100+150, 20, 20) #pusher left + #qp.drawEllipse( ya2-r, 100+150, 20, 20) #pusher right + + #air pad + r,g,b=WndVisualizeARES._lutColor[ap] + rd=13 + r3=int(rd*np.sqrt(3)) + qp.setBrush(QColor(r, g, b, 255)) + qp.drawEllipse(-xa2-rd, ya-4*rd, 2*rd, 2*rd) #left + qp.drawEllipse(-xa2-rd+r3,ya-3*rd, 2*rd, 2*rd) #left + qp.drawEllipse(-xa2-rd+r3,ya-5*rd, 2*rd, 2*rd) #left + + qp.drawEllipse( xa2-rd, ya-4*rd, 2*rd, 2*rd) #right + qp.drawEllipse( xa2-rd-r3,ya-3*rd, 2*rd, 2*rd) #right + qp.drawEllipse( xa2-rd-r3,ya-5*rd, 2*rd, 2*rd) #right + + qp.drawEllipse( -rd, 3*rd, 2*rd, 2*rd) #grating + qp.drawEllipse( 0, 3*rd-r3, 2*rd, 2*rd) #grating + qp.drawEllipse( -2*rd, 3*rd-r3, 2*rd, 2*rd) #grating + + qp.setBrush(br1) + + #deformation compensation + r,g,b=WndVisualizeARES._lutColor[dc] + rd=14 + qp.setBrush(QColor(r, g, b, 255)) + qp.drawEllipse(-xa2, ya2, 2*rd, 2*rd) #air pad left + qp.drawEllipse( xa2-2*rd, ya2, 2*rd, 2*rd) #air pad right + #qp.drawEllipse( -rd, rd, 2*rd, 2*rd) #air pad grating + qp.setBrush(br1) + qp.end() + + + +if __name__ == '__main__': + import argparse + logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + + + def main(): + epilog=__doc__ # +'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n' + parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('--mode', '-m', type=lambda x:int(x, 0), help='mode (see bitmasks) default=0x%(default)x', default=1) + parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0x01) + args=parser.parse_args() + _log.info('Arguments:{}'.format(args.__dict__)) + + app=QApplication(sys.argv) + app._args=args + #devUS=RIXSdevice('RIXS us', ofs=(750, 200), g=-100, s=-1500, sy=-20, cy=70) + #devDS=RIXSdevice('RIXS ds', ofs=(930, 630)) + + if args.mode&0x01: + app._wndVis=WndVisualizeARES() + #if args.mode&0x02: + sys.exit(app.exec_()) + + main() \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a2bd41f --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +.PHONY: dbg install +.DEFAULT_GOAL := install + +REMOTE=$(USER)@satesf-vcons-01 +APP=/sf/furka/applications +BIN=/sf/furka/bin +SRC=$(shell pwd) + + +dbg: + @echo REMOTE $(REMOTE) + @echo APP $(APP) + @echo BIN $(BIN) + @echo SRC $(SRC) + + +install: + @echo "- /EsfRixsApps/.git/\n- /EsfRixsApps/scratch/\n- /**/__pycache__/\n- /**.ipynb*" >/tmp/rsync.filt + @cat /tmp/rsync.filt + rsync -vai --filter='. /tmp/rsync.filt' $(SRC) $(REMOTE):$(APP) + -ssh $(REMOTE) 'for A in ARESvis RIXSconfig spectrumProc; do ln -sF $(APP)/EsfRixsApps/$$A/$$A.py $(BIN)/$$A;done' + + +# @echo MODULE $(MODULE) +# @echo VERSION $(VERSION) +# @echo VERGITCMD "$(VERGITCMD)" +#Examples of usage: +#------------------ +# +#DST_IOC=/ioc/modules/$(MODULE)/$(VERSION)/R7.0.7 +#DST_ANA=/sf/controls/bin/zamofing_t/lib/python3.7/site-packages/gpasciiCommander +#make install +#make install MINORVER=1 +# @echo "+ /Media/\n+ /Media/Videos/\n+ /Media/Videos/*\n- /*\n- /Media/*" >/tmp/rsync.filt +#Versions: +#The generated version is: +# - if not tagged: test +# - if tagged with v*.*: .0 +# - if MINORVER=1 and checked in: v*.*.<# of commits after tag> +#ifdef MINORVER #minot version allowed +#VERGITCMD = git describe --tags --long --match 'v*.*' --dirty 2>/dev/null | sed -e 's/^[^0-9]*//' -e 's/.*-dirty$$/test/' -e 's/-g[0-9a-f]*$$//' -e 's/-/./' +#else +#VERGITCMD = git describe --tags --dirty --match 'v*.*' 2>/dev/null | sed -e 's/^[^0-9]*//' -e 's/.*-dirty$$/$(USER)/' -e 's/.*-g[0-9a-f]*$$/$(USER)/' | sed -r 's/([0-9])$$/\1.0/' +#endif + +#MODULE=$(subst PB_,,$(notdir $(shell pwd))) +#VERSION=$(shell ${VERGITCMD}) +# ssh $(REMOTE) 'ln -s $(APP)/EsfRixsApps/RIXSconfig/RIXSconfig.py $(BIN)/RIXSconfig' +# ssh $(REMOTE) 'ln -s $(APP)/EsfRixsApps/ARESvis/ARESvis.py $(BIN)/ARESvis' +# ssh $(REMOTE) 'ln -s $(APP)/EsfRixsApps/RIXSconfig/RIXSconfig.py $(BIN)/RIXSconfig' +# ssh $(REMOTE) mkdir -p $(DST_IOC) +# rsync -vain --exclude 'Makefile' `git ls-files` $(REMOTE):$(DST_IOC) +# rm -f `find . -name '*.pyc'` +# ssh $(REMOTE) mkdir -p $(DST_IOC) +# ssh $(REMOTE) chmod -R g+w $(DST_IOC) +# ssh $(REMOTE) ln -sf gpasciiCommander.py $(DST_IOC)/gpasciiCommander +# ssh $(REMOTE) python -m compileall $(DST_IOC)/templates +# ssh $(REMOTE) ln -sfT R3.14.12 /ioc/modules/$(MODULE)/$(VERSION)/R7.0.1 +# ssh $(REMOTE) ln -sfT $(VERSION)/R7.0.7 /ioc/modules/$(MODULE)/latest + + + diff --git a/RIXSconfig.png b/RIXSconfig.png new file mode 100644 index 0000000..43b485f Binary files /dev/null and b/RIXSconfig.png differ diff --git a/RIXSconfig/ESF_RIXSconfig.py b/RIXSconfig/ESF_RIXSconfig.py index e4bd685..bb50520 100644 --- a/RIXSconfig/ESF_RIXSconfig.py +++ b/RIXSconfig/ESF_RIXSconfig.py @@ -6,7 +6,7 @@ class Config: # 'R': # 'a0':,'a1':,'a2':,'a3': # polynome for line density of the mirror # 'eqs'{ # - # 'k': , + # 'k': , # 'lut': [# lookup table for starting guess values at a given energy for equation solver # ( , , , ), # ( ... ), diff --git a/RIXSconfig/ESF_RIXS.py b/RIXSconfig/RIXSconfig.py similarity index 100% rename from RIXSconfig/ESF_RIXS.py rename to RIXSconfig/RIXSconfig.py diff --git a/Readme.md b/Readme.md index 5a80639..fdbb5a9 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,25 @@ -spectrumProc -============ +ESF RIXS applications +===================== This repository contains RIXS processing tools for Furka -![alt spectrumProc](snapshot1.png "spectrumProc") \ No newline at end of file +RIXSconfig +---------- + +configure and move the Furka RIXS arm to a desired energy + +![alt RIXSconfig](RIXSconfig.png "RIXSconfig") + +ARESvis +------- + +live visualization of the ARES chamber tools to avoid collisions and beam clipping + +![alt ARESvis](ARESvis.png "ARESvis") + +spectrumProc +------------ + +spectrum post processing tool: + +![alt spectrumProc](spectrumProc.png "spectrumProc") \ No newline at end of file diff --git a/snapshot1.png b/snapshot1.png deleted file mode 100644 index 66e491c..0000000 Binary files a/snapshot1.png and /dev/null differ diff --git a/spectrumProc.png b/spectrumProc.png new file mode 100644 index 0000000..aa4fbf3 Binary files /dev/null and b/spectrumProc.png differ diff --git a/spectrumProc/spectrumProc.py b/spectrumProc/spectrumProc.py index ed6f477..bc80ff1 100755 --- a/spectrumProc/spectrumProc.py +++ b/spectrumProc/spectrumProc.py @@ -7,7 +7,10 @@ # *-----------------------------------------------------------------------* """ -SwissFEL Athos spectrometer +SwissFEL Athos spectrometer data processing + +!!! If there is a problem with library or version, try: !!! +!!! /opt/gfa/python-3.8/latest/bin/python /sf/furka/applications/EsfRixsApps/spectrumProc/spectrumProc.py !!! - select file 'FM_data.pickle' - select run '226' @@ -24,7 +27,9 @@ bitmask for simulation: 0x80: """ - +import os.path +import sys, logging, pickle +_log=logging.getLogger(__name__) from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QLineEdit,\ QCheckBox, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QComboBox, QFileDialog @@ -34,14 +39,15 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure import matplotlib.pyplot as plt -import sys, logging, pickle import numpy as np import scipy -from sklearn.pipeline import Pipeline -from sklearn.preprocessing import PolynomialFeatures -from sklearn.linear_model import LinearRegression,RANSACRegressor +try: + from sklearn.pipeline import Pipeline + from sklearn.preprocessing import PolynomialFeatures + from sklearn.linear_model import LinearRegression,RANSACRegressor +except ModuleNotFoundError as e: + _log.warning(f'sklearn import failed!\n pip install scipy scikit-learn -U --user') -_log=logging.getLogger(__name__) def FitCurv_SPC_Cmos(evnts_j_tot, evnts_i_tot, ROI, MAD_n=4, wnd=None): ''' @@ -270,7 +276,9 @@ if __name__ == '__main__': parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--mode', '-m', type=lambda x:int(x, 0), help='mode (see bitmasks) default=0x%(default)x', default=1) parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0x01) - parser.add_argument("--file", "-f", help="raw data file default=%(default)s", default='FM_data.pickle') + fn=os.path.join(os.path.dirname(os.path.relpath(os.path.realpath(__file__))),'FM_data.pickle') + #fn=os.path.join(os.path.dirname(os.path.realpath(__file__)),'FM_data.pickle') + parser.add_argument("--file", "-f", help="raw data file default=%(default)s", default=fn) args=parser.parse_args() _log.info('Arguments:{}'.format(args.__dict__))