add app_utils for motion_assert, contextMenu og GraphItem, further fixes

This commit is contained in:
2022-09-21 11:06:10 +02:00
parent 158150a954
commit 1393269ceb
11 changed files with 177 additions and 144 deletions

View File

@@ -220,7 +220,7 @@ class WndFixTarget(QWidget):
# def contextMenuEvent(self, event) is called... but this requests to overload the function
# and to build an own context menu
# much simpler is to use Qt.ActionsContextMenu
# and to handle the context in the parent plass
# and to handle the context in the parent class
tree.setContextMenuPolicy(Qt.ActionsContextMenu)

View File

@@ -325,6 +325,12 @@ avoid constand RBV event:
caput SAR-ESPMX:MOT_FX 0.001
caput SAR-ESPMX:MOT_FY 0.001
---------- TODO ----------
caput SAR-ESPMX:MOT_FX.VELO 16
caput SAR-ESPMX:MOT_FY.VELO 16
-faster autofocus:
- move at constant speed and acquire as fast as possible instead stop and go motion
@@ -335,8 +341,18 @@ caput SAR-ESPMX:MOT_FY 0.001
- automation: towards automatic alignement
if idx==0:
#go=UsrGO.Fiducial(bm_pos+bm_sz/2-(20, 20), (40, 40),(fx,fy,bz))
l=.120
go=UsrGO.Fiducial((fx-l/2,fy-l/2), (l, l),bz)
go.sigRegionChangeFinished.connect(self.cb_fiducial_update_z)
grp=self._goTracked
grp.addItem(go)
mft._tree.setData(grp.childItems())
="geometry/find_fiducial"
GEO_FND_FID,GEO_AUTOFOC="geometry/autofocus"

View File

@@ -92,7 +92,7 @@ class AppCfg(QSettings):
if AppCfg.GEO_BEAM_SZ not in keys:
dflt.append((AppCfg.GEO_BEAM_SZ, [30.2, 25.6]))
if AppCfg.GEO_BEAM_POS not in keys:
dflt.append((AppCfg.GEO_BEAM_POS, [23.4, -54.2]))
dflt.append((AppCfg.GEO_BEAM_POS, [23.4, 54.2]))
if AppCfg.GEO_FND_FID not in keys:
dflt.append((AppCfg.GEO_FND_FID, {'sz':101, 'brd':8, 'pitch':120, 'mode':0}))
if AppCfg.GEO_AUTOFOC not in keys:
@@ -307,12 +307,13 @@ verbose bits:
{'name':'set_out', 'title':'use current position as "out"', 'type':'action'},
]},
{'name': AppCfg.DFT_POS_BKLGT, 'title':'Back Light reference positions', 'type':'group','expanded':False, 'children':[
{'name':'pos_in', 'title':'In position', 'value':dft_pos_bklgt.get('pos_in',0), 'type':'float', 'limits':(-30000, 10 ),'step':10, 'decimals':5, 'suffix':'ustep'},
{'name':'pos_out','title':'Out position', 'value':dft_pos_bklgt.get('pos_out',0), 'type':'float', 'limits':(-1000, 10 ),'step':10, 'decimals':5, 'suffix':'ustep'},
{'name':'pos_in', 'title':'In position', 'value':dft_pos_bklgt.get('pos_in',0), 'type':'float', 'limits':(-30000, 10 ),'step':10, 'decimals':5, 'suffix':'ustep'},
{'name':'pos_out', 'title':'Out position', 'value':dft_pos_bklgt.get('pos_out',0), 'type':'float', 'limits':(-30000, 10 ),'step':10, 'decimals':5, 'suffix':'ustep'},
{'name':'pos_diode','title':'Diode pos', 'value':dft_pos_bklgt.get('pos_diode', 0), 'type':'float','limits':(-30000, 10), 'step':10, 'decimals':5, 'suffix':'ustep'},
]},
{'name': AppCfg.DFT_POS_DET, 'title':'detector reference positions', 'type':'group','expanded':False, 'children':[
{'name':'pos_in', 'title':'In position', 'value':dft_pos_det.get('pos_in',0), 'type':'float', 'limits':(-20, 20 ),'step':0.1, 'decimals':5, 'suffix':' mm'},
{'name':'pos_out','title':'Out position', 'value':dft_pos_det.get('pos_out',0), 'type':'float', 'limits':(-20, 20 ),'step':0.1, 'decimals':5, 'suffix':' mm'},
{'name':'pos_in', 'title':'In position', 'value':dft_pos_det.get('pos_in',0), 'type':'float', 'limits':(-200, 200),'step':0.1, 'decimals':5, 'suffix':' mm'},
{'name':'pos_out','title':'Out position', 'value':dft_pos_det.get('pos_out',0), 'type':'float', 'limits':(-200, 200),'step':0.1, 'decimals':5, 'suffix':' mm'},
{'name':'set_in', 'title':'use current position as "in"', 'type':'action'},
{'name':'set_out', 'title':'use current position as "out"', 'type':'action'},
]},

View File

@@ -1,104 +1,85 @@
import time
import logging
import enum
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
_log=logging.getLogger(__name__)
from epics.ca import pend_event
class DeltatauMotorStatus(enum.Flag):
"""names are taken from motorx_all.ui"""
DIRECTION = enum.auto()
MOTION_IS_COMPLETE = enum.auto()
PLUS_LIMIT_SWITCH = enum.auto()
HOME_SWITCH = enum.auto()
UNUSED_1 = enum.auto()
CLOSED_LOOP_POSITION = enum.auto()
SLIP_STALL_DETECTED = enum.auto()
AT_HOME_POSITION = enum.auto()
ENCODER_IS_PRESENT = enum.auto()
PROBLEM = enum.auto()
MOVING = enum.auto()
GAIN_SUPPORT = enum.auto()
COMMUNICATION_ERROR = enum.auto()
MINUS_LIMIT_SWITCH = enum.auto()
MOTOR_HAS_BEEN_HOMED = enum.auto()
AT_SWITCH = MINUS_LIMIT_SWITCH | PLUS_LIMIT_SWITCH
class PositionsNotReached(Exception):
pass
pass
def assert_tweaker_positions(targets, timeout=60.0):
"""
wait for each of the given motors to have specified positions within tolerance
:param targets: [
(motor, target_position, tolerance),
...
]
:return: None
:raises: PositionsNotReached
"""
num_motors = len(targets)
"""
wait for each of the given motors to have specified positions within tolerance
:param targets: [
(motor, target_position, tolerance),
...
]
:return: None
:raises: PositionsNotReached
"""
num_motors=len(targets)
timeisup = timeout + time.time()
timeisup=timeout+time.time()
while time.time() < timeisup:
count = 0
summary = []
for i, m in enumerate(targets):
motor, target, tolerance = m
#name = motor.short_name
#pend_event()
cur = motor.get_rbv()
done = motor.is_done()
#logger.debug("check {}[done={}]: {} == {}".format(name, done, cur, target))
#summary.append("{}[done={}]: {} == {}".format(name, done, cur, target))
if done and tolerance >= abs(cur - target):
count += 1
#pend_event(0.1)
while time.time()<timeisup:
count=0
summary=[]
for i, m in enumerate(targets):
motor, target, tolerance=m
name=motor.short_name
pend_event()
cur=motor.get_position()
done=motor.is_done()
_log.debug("check {}[done={}]: {} == {}".format(name, done, cur, target))
summary.append("{}[done={}]: {} == {}".format(name, done, cur, target))
if done and tolerance>=abs(cur-target):
count+=1
pend_event(0.1)
if count == num_motors:
break
#pend_event(0.1)
if count==num_motors:
break
pend_event(0.1)
if count!=num_motors:
raise PositionsNotReached("failed to reach target positions: {}".format("#".join(summary)))
if count != num_motors:
raise PositionsNotReached("failed to reach target positions: {}".format("#".join(summary)))
def assert_motor_positions(targets, timeout=60.0):
"""
wait for each of the given motors to have specified positions within tolerance
:param targets: [
(motor, target_position, tolerance),
...
]
:return: None
:raises: PositionsNotReached
"""
num_motors = len(targets)
"""
wait for each of the given motors to have specified positions within tolerance
:param targets: [
(motor, target_position, tolerance),
...
]
:return: None
:raises: PositionsNotReached
"""
num_motors=len(targets)
timeisup = timeout + time.time()
timeisup=timeout+time.time()
while time.time() < timeisup:
count = 0
summary = []
for i, m in enumerate(targets):
motor, target, tolerance = m
name = motor._prefix
pend_event()
cur = motor.get_position(readback=True)
done = motor.done_moving
logger.debug("check {}[done={}]: {} == {}".format(name, done, cur, target))
summary.append("{}[done={}]: {} == {}".format(name, done, cur, target))
if done and tolerance >= abs(cur - target):
count += 1
pend_event(0.1)
while time.time()<timeisup:
count=0
summary=[]
for i, m in enumerate(targets):
motor, target, tolerance=m
name=motor._prefix
pend_event()
cur=motor.get_position(readback=True)
done=motor.done_moving
_log.debug("check {}[done={}]: {} == {}".format(name, done, cur, target))
summary.append("{}[done={}]: {} == {}".format(name, done, cur, target))
if done and tolerance>=abs(cur-target):
count+=1
pend_event(0.1)
if count == num_motors:
break
pend_event()
if count==num_motors:
break
pend_event()
if count != num_motors:
raise PositionsNotReached("failed to reach target positions: {}".format("#".join(summary)))
if count!=num_motors:
raise PositionsNotReached("failed to reach target positions: {}".format("#".join(summary)))

View File

@@ -14,13 +14,13 @@ import epics
#from app_utils import assert_motor_positions
class Backlight(object):
def __init__(self, prefix: str='SAR-EXPMX:MOT_BLGT'):
def __init__(self, prefix: str='SAR-EXPMX:MOT_BLGT',pos={'pos_in':-29000,'pos_out':0,'pos_diode':0}):
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}
self._pos = pos
def is_pos(self,strPos):
try:
@@ -29,7 +29,7 @@ class Backlight(object):
_log.info('simulated mode:{}'.format(strPos))
return self._sim['pos']==strPos
m.refresh()
return abs(m.readback - self._pos[strPos]) < 10
return abs(m.readback - self._pos['pos_'+strPos]) < 10
def move(self, strPos, wait=False):
try:
@@ -40,7 +40,7 @@ class Backlight(object):
return
m=self._mot
_log.debug("backlight to {}".format(strPos))
target=self._pos[strPos]
target=self._pos['pos_'+strPos]
m.move(target, ignore_limits=True, wait=wait)
@@ -55,7 +55,7 @@ if __name__ == "__main__":
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'
os.environ['EPICS_CA_ADDR_LIST']='129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066'
_log.info('Arguments:{}'.format(args.__dict__))
bl = Backlight()

View File

@@ -217,7 +217,8 @@ class epics_cam(object):
_log.info('simulated mode:{}'.format(exp))
self._sim['exp']=exp
return
pv_exp.put(exp, wait=True)
self.update_params((pv_exp,exp))
#pv_exp.put(exp, wait=True)
def get_exposure(self):
try:

View File

@@ -10,7 +10,7 @@ from PyQt5.uic import loadUiType
from epics import Motor
from epics.ca import pend_event
from app_utils import DeltatauMotorStatus, assert_motor_positions
from app_utils import assert_motor_positions
Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui')
SPMG_STOP = 0
@@ -138,12 +138,6 @@ class MotorTweak(QWidget, Ui_MotorTweak):
self._val=v=m.get('VAL')
return v
def is_homed(self):
m=self._motor
m.refresh()
status = DeltatauMotorStatus(m.motor_status)
return bool(DeltatauMotorStatus.MOTOR_HAS_BEEN_HOMED & status)
def move_abs(self, drive=None, wait=False, assert_position=False):
if drive is None:
drive = float(self._drive_val.text())

View File

@@ -7,8 +7,7 @@ from PyQt5.QtWidgets import QMenu, QInputDialog, QAction
from PyQt5.uic import loadUiType
from epics import PV
from epics.ca import pend_event
#from app_utils import assert_tweaker_positions #ZAC: orig. code
from motor_utils import assert_tweaker_positions
Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui')
logger = logging.getLogger(__name__)

View File

@@ -85,7 +85,7 @@ class Jungfrau:
_log.warning(f'Jungfrau not connected: {e}')
self._daq=None
self._pv_pulse_id=epics.PV('SAR-EXPMX-EVR0:RX-PULSEID')
self._pv_pulse_id.connect()
def acquire(self,run_name, n_pulses, wait=False):
if self._daq is not None:
@@ -94,4 +94,6 @@ class Jungfrau:
def gather_upload(self):
self._pulse_id_end=self._pv_pulse_id.value
_log.debug(f'pulse_id: {self._pulse_id_start}..{self._pulse_id_end}')

View File

@@ -20,6 +20,8 @@ from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from PyQt5.QtGui import QPolygon,QPolygonF
from PyQt5.QtCore import Qt,QPointF,QLineF
from PyQt5.QtWidgets import QMenu
def itr2str(itr):
return '('+', '.join(tuple(map(lambda x:f'{x:.6g}', itr)))+')'
@@ -145,10 +147,28 @@ class Marker(pg.ROI):
p.setTransform(tr)
return ofx,ofy
class Fiducial(pg.ROI):
def __init__(self, pos, size, z:float, **kargs):
class UsrROI(pg.ROI):
def __init__(self, pos, size, **kargs):
pg.ROI.__init__(self, pos, size, **kargs)
def cb_toggle_movable(self):
self.translatable=not self.translatable
def contextMenuEvent(self, event):
#pg.ROI.contextMenuEvent(self,event)
_log.debug('ContextMenu')
menu = QMenu("ctx")
act=menu.addAction('locked')
act.setCheckable(True)
act.setChecked(not self.translatable)
act.triggered.connect(self.cb_toggle_movable)
#menu.addAction('center in view')
#menu.addAction('delete')
menu.exec(event.screenPos())
class Fiducial(UsrROI):
def __init__(self, pos, size, z:float, **kargs):
UsrROI.__init__(self, pos, size, **kargs)
self._z=z
def paint(self, p, *args):
@@ -195,7 +215,7 @@ class Fiducial(pg.ROI):
return jsn
class Grid(pg.ROI):
class Grid(UsrROI):
'''a grid'''
def __init__( self, pos=(0,0), size=(30,20), cnt=(6,4), fiducialSize=.2, **kargs):
@@ -271,7 +291,7 @@ class Grid(pg.ROI):
return pts
class Path(pg.ROI):
class Path(UsrROI):
'''
a path object with fiducials
path=np.array(n,2) of path points
@@ -347,7 +367,7 @@ class Path(pg.ROI):
}
return jsn
class FixTargetFrame(pg.ROI):
class FixTargetFrame(UsrROI):
'''fixed target frame'''
tpl={
'test':{
@@ -664,7 +684,7 @@ if __name__=='__main__':
pass
else:
obj_info(o)
print(o.state)
#print(o.state)
## Create image to display
@@ -689,7 +709,7 @@ if __name__=='__main__':
w=pg.GraphicsLayoutWidget(show=True, size=(1000, 800), border=True)
w.setWindowTitle('pyqtgraph example: ROI Examples')
vb=w.addViewBox(row=1, col=0, lockAspect=True,invertY=False)
vb=w.addViewBox(row=1, col=0, lockAspect=True,invertY=False,enableMenu=False)
vb.enableAutoRange(enable=False)
try:
g=pg.GridItem(pen=(0, 255, 0), textPen=(0, 255, 0)) # green grid and labels
@@ -748,9 +768,13 @@ if __name__=='__main__':
vi=FixTargetFrame((400,-200),(400,400),tpl='12.5x12.5')
vb.addItem(vi)
vi=Fiducial((0,200),(40,40),3)
vi=Fiducial((0,200),(40,40),3,movable=False,removable=True)
vb.addItem(vi)
vi=pg.PolyLineROI([(22, -19), (40, -30), (23, -10), (22, -19)], closed=True)
vb.addItem(vi)
viRoi=pg.ROI([-200, -200], [100, 80],movable=True, rotatable=True, resizable=True)
viRoi.addFreeHandle(pos=[.7, .5], axes=None, item=None, name=None, index=None) # rechteck , frei beweglich ??? verschwinden anch bewegung

View File

@@ -5,10 +5,10 @@
# | 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
@@ -18,7 +18,7 @@ bitmask for simulation:
0x10: Deltatau motors
0x20: SmarAct motors
'''
"""
import logging
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
@@ -439,9 +439,6 @@ class WndSwissMx(QMainWindow, Ui_MainWindow):
self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_S), self)
self.shortcut.activated.connect(self.cb_save_cam_image)
self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_T), self)
self.shortcut.activated.connect(lambda: qutilities.toggle_warn(SKIP_ESCAPE_TRANSITIONS_IF_SAFE))
for k in range(10):
qkey = k + Qt.Key_0
self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + qkey), self)
@@ -632,11 +629,12 @@ class WndSwissMx(QMainWindow, Ui_MainWindow):
_log.debug('TODO: check to not connect smultiple igNewCamImg')
if index > 0: # not showing camera image
_log.warning("listening to zescape")
self.timer.stop()
zescape.start_listening()
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.check_zescape)
self.timer.start(20)
pass
#self.timer.stop()
#zescape.start_listening()
#self.timer = QtCore.QTimer(self)
#self.timer.timeout.connect(self.check_zescape)
#self.timer.start(20)
else:
app=QApplication.instance()
try:
@@ -1461,15 +1459,16 @@ class WndSwissMx(QMainWindow, Ui_MainWindow):
vb.removeItem(go)
tmpGoLst.clear()
n=int(p.shape[0]/100)+1
for i in range(0,p.shape[0],n):
fx,fy=p[i, :]/1000
fx=-fx #X axis has inverted sign !
l=.06
go=UsrGO.Fiducial((fx-l/2, fy-l/2), (l, l), i)
grp.addItem(go)
tmpGoLst.append(go)
mft._tree.setData(grp.childItems())
# adding debug fiducials
#n=int(p.shape[0]/100)+1
#for i in range(0,p.shape[0],n):
# fx,fy=p[i, :]/1000
# fx=-fx #X axis has inverted sign !
# l=.06
# go=UsrGO.Fiducial((fx-l/2, fy-l/2), (l, l), i)
# grp.addItem(go)
# tmpGoLst.append(go)
#mft._tree.setData(grp.childItems())
#_log.debug(f'first point:{p[0,:]}')
#_log.debug(f'step to 2nd point:{p[1,:]-p[0,:]}')
@@ -1541,7 +1540,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow):
app=QApplication.instance()
steps = [
# lambda: sample_selection.tell.set_current(30.0),
lambda: self.move_collimator("ready"),
lambda: self.move_collimator("out"),
lambda:self.move_detector("out"),
lambda:self.move_post_tube("out"),
lambda:app._backlight.move("in"),
@@ -2080,14 +2079,20 @@ Author Thierry Zamofing (thierry.zamofing@psi.ch)
j=0
elif type(go)==UsrGO.Fiducial and go.size()[0]==0.12:
ptFitTrf[j]=go.pos()+go.size()/2
if j<ptFitTrf.shape[0]:
ptFitTrf[j]=go.pos()+go.size()/2
j+=1
ptFitPlane[i]=go.ctr()
i+=1;j+=1
i+=1
app=QApplication.instance()
geo=app._geometry
if n>=3:
geo.least_square_plane(ptFitPlane)
def cb_progress(self,i,sz,dlg):
dlg.setValue(int(sz*90/i))
if dlg.wasCanceled():
raise AttributeError('canceled')
def daq_collect(self, **kwargs):# points, visualizer_method, visualizer_params):
#def _OLD_daq_collect_points(self, points, visualizer_method, visualizer_params):
@@ -2134,14 +2139,21 @@ Author Thierry Zamofing (thierry.zamofing@psi.ch)
sp.setup_coord_trf() # reset to shape path system
# sp.meta['pt2pt_time']=10 #put between setup_sync and setup_motion to have more motion points than FEL syncs
dlg.setLabelText("Download motion program");dlg+=5
try:
sp.comm.gpascii._cb_func=lambda i, sz:self.cb_progress(i, sz, dlg)
except AttributeError:
pass
sp.setup_motion(fnPrg=fn+'.prg', mode=3, scale=1., dwell=10)
dlg.setLabelText("Homing and get ready");dlg+=35
if dlg.wasCanceled():
return
dlg.setLabelText("Homing and get ready");dlg+=5
sp.homing() # homing if needed
sp.run() # start motion program
sp.wait_armed() # wait until motors are at first position
sp.trigger(0.5) # send a start trigger (if needed) after given time
#PV with the current pulse
# [sf-lc7a ~]$ caget
jf.acquire('filename.tmp',sp.points.shape[0])
if not dt._comm is None:
dlg.setLabelText("run motion/acquisition")
@@ -3906,10 +3918,13 @@ if __name__=="__main__":
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)
app._backlight = backlight.Backlight(None,dft_pos_bklgt)
else:
app._backlight = backlight.Backlight()
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)