From 3815a07cdfa2a62aab4b3c5dbafe8d00b274b3e1 Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Thu, 14 Jul 2022 09:16:29 +0200 Subject: [PATCH] add files and simulated modes --- .gitignore | 1 + Readme.md | 5 +++ backlight.py | 70 +++++++++++++++++++++++++++++++++++++++++ camera.py | 28 +++++++++++++++-- geometry.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ illumination.py | 30 +++++++++++++----- zoom.py | 63 ++++++++++++++++++++++++++++--------- zoom.ui | 50 ++++++++++++++++++++++++++++++ 8 files changed, 305 insertions(+), 24 deletions(-) create mode 100644 .gitignore create mode 100755 backlight.py create mode 100644 geometry.py create mode 100644 zoom.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/Readme.md b/Readme.md index e859b3d..b3d1616 100644 --- a/Readme.md +++ b/Readme.md @@ -146,3 +146,8 @@ p.put(a) print(p.get()) https://pyepics.github.io/pyepics/arrays.html + + +EPICS_CA_ADDR_LIST='129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066' +cd /home/zamofing_t/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python/oldRepos/app/src +python swissmx.py diff --git a/backlight.py b/backlight.py new file mode 100755 index 0000000..6018b9d --- /dev/null +++ b/backlight.py @@ -0,0 +1,70 @@ +#!/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) | +# *-----------------------------------------------------------------------* + +import logging +_log=logging.getLogger(__name__) +import epics + +#from app_config import settings +#from app_utils import assert_motor_positions + +class Backlight(object): + def __init__(self, prefix: str='SAR-EXPMX:MOT_BLGT'): + if prefix is None: + self._sim={'pos':'out'} + _log.info('simulated mode:{}'.format(self._sim)) + return #simulated mode + self._mot = epics.Motor(prefix) + self._pos = {'in':-29000,'out':0} + + def is_pos(self,strPos): + try: + m=self._mot + except AttributeError: + _log.info('simulated mode:{}'.format(strPos)) + return self._sim['pos']==strPos + m.refresh() + return abs(m.readback - self._pos[strPos]) < 10 + + def move(self, strPos, wait=False): + try: + m=self._mot + except AttributeError: + _log.info('simulated mode:{}'.format(strPos)) + self._sim['pos']=strPos + return + m=self._mot + _log.debug("backlight to {}".format(strPos)) + target=self._pos[strPos] + m.move(target, ignore_limits=True, wait=wait) + + + +if __name__ == "__main__": + import time,os + import argparse + logging.basicConfig(level=logging.DEBUG,format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--mode', help='mode string with characters: "frgbka" for front,red,green,blue,back,all') + parser.add_argument("-t", "--test", help="test sequence", action="store_true") + + args = parser.parse_args() + os.environ['EPICS_CA_ADDR_LIST'] = '129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066' + + _log.debug(args) + bl = Backlight() + if args.test: + bl.move('in') + time.sleep(2) + bl.move('out') + time.sleep(2) + bl.move('in') + time.sleep(2) + bl.move('out') + time.sleep(2) diff --git a/camera.py b/camera.py index a4107f0..a31c458 100755 --- a/camera.py +++ b/camera.py @@ -1,4 +1,10 @@ #!/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) | +# *-----------------------------------------------------------------------* """ Hi Zac, @@ -38,7 +44,11 @@ class CameraStatus(enum.IntEnum): class epics_cam(object): - def __init__(self, prefix="ESB-MX-CAM"): + def __init__(self, prefix='SARES30-CAMS156-SMX-OAV'): + if prefix is None: + self._sim={'exp':3.1315} + _log.info('simulated mode:{}'.format(self._sim)) + return #simulated mode self._pv=pv=dict() if prefix[-1]!=':': prefix+=':' self._prefix=prefix @@ -132,9 +142,21 @@ class epics_cam(object): self._sz=(int(pv_w.value), int(pv_h.value)) def set_exposure(self,exp): - pv_exp=self.getPv('EXPOSURE') + try: + pv_exp=self.getPv('EXPOSURE') + except AttributeError: + _log.info('simulated mode:{}'.format(exp)) + self._sim['exp']=exp + return pv_exp.put(exp, wait=True) - pass + + def get_exposure(self): + try: + pv_exp=self.getPv('EXPOSURE') + except AttributeError: + exp=self._sim['exp'];_log.info('simulated mode:{}'.format(exp)) + return exp + return pv_exp.get() def set_roi(self,rxs,rxe,rys,rye): pv_rxs=self.getPv('REGIONX_START');pv_rxe=self.getPv('REGIONX_END') diff --git a/geometry.py b/geometry.py new file mode 100644 index 0000000..2e0507f --- /dev/null +++ b/geometry.py @@ -0,0 +1,82 @@ +#!/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) | +# *-----------------------------------------------------------------------* +''' +coordinate systems, optical center, xray axis, pixel sizes etc. +''' + +class gepmetry: + + def __init__(self): + pass + def find_optical_center(p): + # p is an array of + # at zoom out: (p1x,p1y),(p2x,p2y),(p3x,p3y),... + # at zoom in : (p1x,p1y),(p2x,p2y),(p3x,p3y),... + # + # the pixel positions are given in chip pixel coordinates (0/0= top/right) + # and ignore roi and binning + # the returned (cx,cy) is the pixel koordinate that does not change with zoom + # this coordinate represents also the origin of other coordinates + pass + + def zoom2pixsz(zoom): + # this returns the pixel size at a given zoom level + # the returned value is a 2x2 matrix: + # [pxx pxy] + # [pyx pyy] which is interpolated out of a lookup table + # + # [pxx pxy] [nx] + # [pyx pyy]*[ny] results in a vector in meter of a vector [nx,ny] pixels in x and y direction + pass + + def set_zoom2pixsz(): + #tx: 2d-vector in m when moving px pixel in x direction + #ty: 2d-vector in m when moving py pixel in y direction + # the _lut_z2p is dictionaty a lookuptable + # zoom {1,200,400,600,800,1000} + #[pxx pxy] + #[pyx pyy] + self._lut_z2p=_lut_z2p={ + 'zoom': np.array((1,200,400,600,800,1000),dtype=np.float32), + 'pixsz': np.array( + #((pxx,pxy),(pyx,pyy)), # zoom n + (( 1,0),(0, 1)), # zoom 1 + (( 2,0),(0, 2)), # zoom 200 + (( 4,0),(0, 4)), # zoom 400 + (( 6,0),(0, 6)), # zoom 600 + (( 8,0),(0, 8)), # zoom 800 + ((10,0),(0,10)), # zoom 1000 + dtype=np.float32)} + + def autofocus(): + # cam camera object + # mot motor object + # rng region (min max relative to current position) to seek + # n number of images to take in region + # roi region of interrest to calculate sharpness + # mode mode to calculate sharpness (sum/max-min/hist? of edge detection in roi) + pass + + def pix2pos(p,zoom=None): + # returns the position m(x,y) in meter relative to the optical center at a given zoom level of the pixel p(x,y) + # if zoom=None, the last zoom value is used + pass + + def pos2pix(p,zoom=None): + # returns the pixel p(x,y) of the position m(x,y) in meter relative to the optical center at a given zoom level + # if zoom=None, the last zoom value is used + pass + + def optctr2xray(): + # returns the vector m(x,y) of the optical center to the xray + pass + + + + + diff --git a/illumination.py b/illumination.py index 4e40c99..3727913 100755 --- a/illumination.py +++ b/illumination.py @@ -1,4 +1,10 @@ #!/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) | +# *-----------------------------------------------------------------------* import logging _log=logging.getLogger(__name__) @@ -39,7 +45,11 @@ all = 0xff class IlluminationControl(object): - def __init__(self,hostname: str,port: int): + def __init__(self,hostname: str="129.129.221.92",port: int=1003): + if hostname is None: #simulated mode + self._sim={'stat':0} + _log.info('simulated mode:{}'.format(self._sim)) + return self._hostname = hostname self._port = port self.connect() @@ -59,14 +69,15 @@ class IlluminationControl(object): def is_on(self, led_flags: int) -> bool: """check status for light state, see CONSTANTS STATUS_BITS_*""" - try: - s = self.status()[0] - except: - print("error reading status") - return False + s = self.status() return bool(s & led_flags) def send_command(self, cmd: bytes): + try: + s=self._socket + except AttributeError: #simulated mode + _log.info('simulated mode:{}'.format(cmd)) + return try: self._socket.sendall(cmd) except (BrokenPipeError, OSError) as e: @@ -75,9 +86,14 @@ class IlluminationControl(object): try: self._socket.sendall(cmd) except Exception as e: - raise IlluminationControlException("unrecoverable socket error: {}".format(e.args)) + _log.error("unrecoverable socket error: {}".format(e.args)) def status(self): + try: + s=self._socket + except AttributeError: #simulated mode + _log.info('simulated mode') + return self._sim['stat'] self.send_command(bytes((stat,all))) resp = self._socket.recv(1024) return resp[0]&0x1f diff --git a/zoom.py b/zoom.py index 326852c..c43c772 100755 --- a/zoom.py +++ b/zoom.py @@ -1,5 +1,12 @@ #!/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) | +# *-----------------------------------------------------------------------* +''' +''' import json import logging @@ -25,7 +32,7 @@ from PyQt5.QtWidgets import ( from PyQt5.uic import loadUiType import epics -Ui_Zoom, QWidget = loadUiType("epics_widgets/zoom.ui") +Ui_Zoom, QWidget = loadUiType("zoom.ui") MIN_ZOOM = 1 MAX_ZOOM = 1000 SPINNER_SINGLE_STEP = 50 @@ -199,7 +206,7 @@ class Zoom(QGroupBox, Ui_Zoom): def toggle_backlight(self): bl=QApplication.instance()._backlight - if bl.is_in() or bl.is_diode(): + if bl.is_pos('in'): self.moveBacklight.emit("out") self.blgt_button.setText("Move Backlight IN") else: @@ -277,6 +284,9 @@ class Zoom(QGroupBox, Ui_Zoom): class QopticZoom(object): def __init__(self, prefix='SAR-EXPMX-FETURA'): + if prefix is None: + self._val=1 + return #simulated mode self._pv=pv=dict() if prefix[-1]!=':': prefix+=':' self._prefix=prefix @@ -291,32 +301,57 @@ class QopticZoom(object): return pv def get(self) -> int: - pv=self.getPv('POS_RB') + try: + pv = self.getPv('POS_RB') + except AttributeError: + val=self._val; _log.info('simulated mode:{}'.format(val)) + return val + else: + pv=self.getPv('POS_RB') return pv.get() def set(self, val: int): - - pv=self.getPv('POS_SP') - pv.put(val) + try: + pv=self.getPv('POS_SP') + except AttributeError: + _log.info('simulated mode:{}'.format(val)) + self._val=val #simulated mode + else: + pv.put(val) if __name__ == "__main__": + import time, os, PIL.Image, platform, subprocess + import argparse + logging.basicConfig(level=logging.DEBUG,format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + + parser = argparse.ArgumentParser() + parser.add_argument("--sim", "-s", help="simulate all devices", action='store_true') + args = parser.parse_args() + + import sys import backlight import illumination import camera + _log.info('Start') + os.environ['EPICS_CA_ADDR_LIST'] = '129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066' + app = QApplication(sys.argv) from app_config import settings, appsconf - logging.basicConfig(level=logging.DEBUG,format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + if args.sim: + app._backlight = backlight.Backlight(None) + app._illumination = illumination.IlluminationControl(None) + app._zoom = QopticZoom(None) + app._camera = camera.epics_cam(None) + else: + app._backlight = backlight.Backlight() + app._illumination = illumination.IlluminationControl() + app._zoom = QopticZoom() + app._camera = camera.epics_cam() - _log.debug('Start') - app = QApplication(sys.argv) - app._backlight = backlight.Backlight() - app._illumination = illumination.IlluminationControl("129.129.221.92", 1003) - app._zoom = QopticZoom() - app._camera = camera.camera_server('SARES30-CAMS156-SMX-OAV') simulated = appsconf["microscope"]["zoom"].get("simulate", False) diff --git a/zoom.ui b/zoom.ui new file mode 100644 index 0000000..445e358 --- /dev/null +++ b/zoom.ui @@ -0,0 +1,50 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Lighting && Camera + + + + + + + Zoom + + + + + + + +