This commit is contained in:
2022-07-18 09:11:46 +02:00
parent 079c9dbc4c
commit a4217ef1c8
3 changed files with 366 additions and 35 deletions

View File

@@ -18,7 +18,6 @@ SPMG_PAUSE = 1
SPMG_MOVE = 2
SPMG_GO = 3
class MotorTweak(QWidget, Ui_MotorTweak):
event_val = pyqtSignal(str, dict)
event_readback = pyqtSignal(str, float, dict)
@@ -335,11 +334,7 @@ class MotorTweak(QWidget, Ui_MotorTweak):
qp.end()
def _draw_limits(self, qp):
try:
m = self.motor
except AttributeError:
_log.warning('Motor not connected')
return
m = self.motor
width, height = self.size().width(), self.size().height()
pad = 5
rounding = 2

View File

@@ -0,0 +1,309 @@
#!/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) |
# *-----------------------------------------------------------------------*
# simulated motor tweaks ui:
# same interface as MotorTweak and SmaractMotorTweaks, but mimes just a simulated motor
import logging
from time import sleep
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QBrush, QColor, QPainterPath, QPen, QDoubleValidator
from PyQt5.QtWidgets import QMenu, QInputDialog, QAction
from PyQt5.uic import loadUiType
from epics.ca import pend_event
Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui')
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class SimMotor:
def __init__(self,motor_base, short_name):
self._llm = -10
self._hlm = 10
self._prec = 5
self._units = 'mm'
self._pos = 3.1415
self._short_name=short_name
class SimMotorTweak(QWidget, Ui_MotorTweak):
event_val = pyqtSignal(str, dict)
event_readback = pyqtSignal(str, dict)
event_soft_limit = pyqtSignal(str, dict)
event_high_hard_limit = pyqtSignal(str, dict)
event_low_hard_limit = pyqtSignal(str, dict)
event_axis_fault = pyqtSignal(str, dict)
def __init__(self, parent=None):
super(SimMotorTweak, self).__init__(parent=parent)
self.setupUi(self)
self._wheel_tweaks = True
self._auto_labels = True
self._locked = False
self._label_style = 'basic'
self._templates_source = {
'basic': '<b>{short_name}</b> <font color="#080">{{rbv:.{precision}f}} {units}</font>',
'small': '<small>{short_name} <font size="small" color="#080">{{rbv:.{precision}f}} {units}</font><small>',
'2 lines': '<b>{short_name}</b><br><font size="small" color="#080">{{rbv:.{precision}f}} {units}</font>',
'busy': '<b>{short_name}</b> <font color="#080">{{rbv:.{precision}f}} {units}</font>'
}
self._templates = {}
def connect_motor(self, motor_base, short_name=None, *args, **kwargs):
self.label.setToolTip('{} => {}'.format(motor_base, motor_base))
self.motor=SimMotor(motor_base, short_name)
twv=0.1
self.set_motor_validator()
self._drive_val.setText(str(self.motor._pos))
self._drive_val.returnPressed.connect(self.move_motor_to_position)
self.jog_forward.hide()
self.jog_reverse.hide()
tweak_min = kwargs.get("tweak_min", 0.0001)
tweak_max = kwargs.get("tweak_max", 20.0)
self._tweak_val.setText(str(twv))
self._tweak_val.setValidator(QDoubleValidator(tweak_min, tweak_max, 4, self._tweak_val))
#self._tweak_val.editingFinished.connect(lambda m=self._pv_tweak_val: m.put(self._tweak_val.text()))
#self.tweak_forward.clicked.connect(lambda m: self._pv_tweak_f.put(1))
#self.tweak_reverse.clicked.connect(lambda m: self._pv_tweak_r.put(1))
self.bind_wheel()
self.update_label_template()
#self._pv_readback.add_callback(self.update_label)
#self._pv_status.add_callback(self.set_val_on_status_change)
# self._pv_drive.add_callback(self.set_val)
#self._pv_drive.add_callback(self.emit_signals, source_field='VAL')
#self._pv_readback.add_callback(self.emit_signals, source_field='RBV')
#self._pv_hlm.add_callback(self.emit_signals, source_field='HLS')
#self._pv_llm.add_callback(self.emit_signals, source_field='LLS')
# m.set_callback('HLM', self.set_motor_validator)
def set_motor_validator(self, **kwargs):
lineedit = self._drive_val
min, max = self.motor._llm, self.motor._hlm
if min == max:
min = -1e6
max = 1e6
lineedit.setValidator(QDoubleValidator(min, max, 5, lineedit))
def move_relative(self, dist, delay=None):
target = self.motor._pos + dist
if delay:
sleep(delay)
def get_position(self):
return self.motor._pos
def is_homed(self):
return True
def get_status(self, as_string=False):
"""
States:
0 Stopped
1 Stepping
2 Scanning
3 Holding
4 Targeting
5 Move Delay
6 Calibrating
7 Finding Ref
8 Locked
:return:
"""
return 0
def wait(self):
pend_event(.01)
while not self.is_done():
pend_event(0.2)
def is_done(self):
return True # in (0, 3)
def move_motor_to_position(self, drive=None, wait=False, assert_position=False):
if assert_position:
wait=True
if drive is None:
logger.debug('{} abs target from widget'.format(self.short_name))
drive = float(self._drive_val.text())
logger.debug('{} abs move => {}'.format(self.short_name, drive))
self._pos=drive
def emit_signals(self, **kw):
field = kw['source_field']
if field == 'VAL':
self.event_val.emit(self._pvname, kw)
elif field == 'RBV':
self.event_readback.emit(self._pvname, kw)
elif field == 'LVIO':
self.event_soft_limit.emit(self._pvname, kw)
elif field == 'HLS':
self.event_high_hard_limit.emit(self._pvname, kw)
self.event_axis_fault.emit(self._pvname, kw)
elif field == 'LVIO':
self.event_low_hard_limit.emit(self._pvname, kw)
self.event_axis_fault.emit(self._pvname, kw)
elif field == 'STAT':
self.event_axis_fault.emit(self._pvname, kw)
def set_val_on_status_change(self, **kw):
'''
0 Stopped
1 Stepping
2 Scanning
3 Holding
4 Targeting
5 Move Delay
6 Calibrating
7 Finding Ref
8 Locked
:param kw:
:return:
'''
status = kw['char_value']
if status in ('Holding', 'Stopped'):
v = self._pv_readback.get(as_string=True)
logger.debug('updating VAL on status change to holding/stopped = {}'.format(v))
self._drive_val.setText(v)
def set_val(self, **kw):
v = kw['char_value']
logger.debug('updating VAL = {}'.format(v))
self._drive_val.setText(v)
def update_label(self, **kwargs):
self.label.setText(self._templates[self._label_style].format(rbv=self._pv_readback.get()))
self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(self._pv_tweak_val.get(), self._units))
self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(self._pv_tweak_val.get(), self._units))
def tweak_event(self, event):
sign = event.angleDelta().y()
if sign < 0:
self._pv_tweak_r.put(1)
else:
self._pv_tweak_f.put(1)
def bind_wheel(self):
self.tweak_forward.wheelEvent = self.tweak_event
self.tweak_reverse.wheelEvent = self.tweak_event
def contextMenuEvent(self, event):
name = self._pv_name.get()
unit = self._units
prec = self._prec
menu = QMenu(self)
menu.setTitle(self.short_name)
lockmotor = QAction('lock motor', menu, checkable=True)
lockmotor.setChecked(self._locked)
menu.addAction(lockmotor)
autolabelsAction = QAction('auto', menu, checkable=True)
autolabelsAction.setChecked(self._auto_labels)
menu.addAction(autolabelsAction)
wheel_tweaks = QAction('Mouse wheel tweaks motor', menu, checkable=True)
wheel_tweaks.setChecked(self._wheel_tweaks)
menu.addAction(wheel_tweaks)
# stopmotorAction = QAction('Stopped', menu, checkable=True)
# stopmotorAction.setChecked(SPMG_STOP == m.stop_go)
# menu.addAction(stopmotorAction)
changeprecAction = menu.addAction("Change Precision")
changetweakstepAction = menu.addAction("Tweak step {:.3f} {}".format(self._pv_tweak_val.get(), unit))
tozeroAction = menu.addAction("move to Zero")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == lockmotor:
self._locked = not self._locked
if self._locked:
self._controlbox.setDisabled(True)
else:
self._controlbox.setDisabled(False)
elif action == changeprecAction:
msg = 'Precision for motor {}'.format(name)
logger.debug('prec before %d', prec)
prec, ok = QInputDialog.getInt(self, msg, msg, prec, 0, 10)
logger.debug('prec after (%d) %d', ok, prec)
if ok:
self._prec = prec
elif action == changetweakstepAction:
tv = self._pv_tweak_val.get()
msg = 'Tweak step for motor {} at {} {}'.format(name, tv, unit)
tv, ok = QInputDialog.getDouble(self, msg, msg, tv, pow(10, -prec), 10.0, prec)
if ok:
self._pv_tweak_val.put(tv)
elif action == tozeroAction:
self._pv_drive.put(0.0)
elif action == autolabelsAction:
self._auto_labels = not self._auto_labels
elif action == wheel_tweaks:
self._wheel_tweaks = not self._wheel_tweaks
self.bind_wheel()
self.update_label_template()
def resizeEvent(self, event):
pass
def update_label_template(self):
source = self._templates_source
target = self._templates
for k in source:
target[k] = source[k].format(
short_name=self.motor._short_name,
precision=self.motor._prec,
units=self.motor._units)
self.label.setText(target[self._label_style].format(rbv=self.motor._pos))
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing)
self._draw_limits(qp)
qp.end()
def _draw_limits(self, qp):
pass
# width, height = self.size().width(), self.size().height()
# pad = 5
# rounding = 2
# size = 10
# if m.HLS:
# x, y, w, h = width - size, pad, size, height - 2 * pad
# elif m.LLS:
# x, y, w, h = 0, pad, size, height - 2 * pad
# else:
# return
# color = QColor('indianred')
# qp.setBrush(QBrush(color, Qt.SolidPattern))
# qp.setPen(QPen(color))
# path = QPainterPath()
# path.setFillRule(Qt.WindingFill)
# path.addRoundedRect(x, y, w, h, rounding, rounding)
# qp.drawPath(path)

View File

@@ -77,7 +77,7 @@ TASK_HELICAL = "helical"
TASK_EMBL = "embl"
#import PrelocatedCoordinatesModel #ZAC: orig. code
import PrelocatedCoordinatesModel
#from EmblModule import EmblWidget #ZAC: orig. code
#from HelicalTable import HelicalTableWidget #ZAC: orig. code
#from Wigis import Spinner, Checkbox #ZAC: orig. code
@@ -105,12 +105,15 @@ from CustomROI import BeamMark
#from epics_widgets import zoom #ZAC: orig. code
from epics_widgets.MotorTweak import MotorTweak
from epics_widgets.SmaractMotorTweak import SmaractMotorTweak
from epics_widgets.SimMotorTweak import SimMotorTweak
from matplotlib import pyplot
import numpy as np
import pyqtgraph as pg
import pyqtgraph.exporters
pg.setConfigOption("antialias", True)
# use antialias for draw lines, interpret image data as row-major instead of col-major
pg.setConfigOptions(antialias=True,imageAxisOrder='row-major')
import app_utils
from app_config import settings, appsconf, option, toggle_option, simulated
@@ -222,6 +225,8 @@ class Main(QMainWindow, Ui_MainWindow):
super(Main, self).__init__()
self.setupUi(self)
app=QApplication.instance()
if os.getenv("DEVELOPMENT_VERSION"):
title = "[DEVELOPMENT] SwissMX - Solid support scanning for FEL sources"
else:
@@ -251,13 +256,13 @@ class Main(QMainWindow, Ui_MainWindow):
self.microscope_page.layout().addWidget(self.glw)
self.glw.show()
self.vb = self.glw.addViewBox(enableMenu=False)
self.vb = self.glw.addViewBox(invertY=True)#,enableMenu=False)
self.img = pg.ImageItem()
self.img._swissmx_name = "microscope_image_item"
# self.graphicsView.setCentralItem(self.vb)
self.glw.scene().sigMouseMoved.connect(self.mouse_move_event)
self.glw.scene().sigMouseClicked.connect(self.mouse_click_event)
self.vb.setAspectLocked()
self.vb.setAspectLocked(True)
self.vb.setBackgroundColor((120, 90, 90))
self.vb.addItem(self.img)
@@ -330,9 +335,9 @@ class Main(QMainWindow, Ui_MainWindow):
#self.create_helical_widgets() #ZAC: orig. code
self.center_piece_update(0) # start camera updater
#curzoom = qoptic_zoom.get_position(cached=False) #ZAC: orig. code
#_log.debug(f"starting app with zoom at {curzoom}")
#self.zoom_changed_cb(curzoom)
curzoom = app._zoom.get()
_log.debug(f"starting app with zoom at {curzoom}")
self.zoom_changed_cb(curzoom)
self._tabs_daq_methods.currentChanged.connect(self.switch_task)
self.switch_task()
@@ -806,7 +811,7 @@ class Main(QMainWindow, Ui_MainWindow):
exp_tab.layout().addStretch()
# DAQ Methods Tabs
#self.build_daq_methods_prelocated_tab() #ZAC: orig. code
self.build_daq_methods_prelocated_tab() #ZAC: orig. code
self.build_daq_methods_grid_tab()
self.build_sample_selection_tab()
@@ -1037,17 +1042,20 @@ class Main(QMainWindow, Ui_MainWindow):
self._lb_coords.setText("")
def mouse_move_event(self, pos):
app = QApplication.instance()
self._mouse_pos = pos
task = self.active_task()
xy = self.img.mapFromScene(pos)
z = qoptic_zoom.get_position()
z = app._zoom.get()
_log.debug('mouse_pos:{} scene_pos:{} zoom:{}'.format(pos,xy,z))
#TODO: implement mouse handling
# if self._ppm_toolbox._force_ppm > 0:
# ppm = self._ppm_toolbox._force_ppm
# else:
try:
ppm = self.ppm_fitter(z)
except:
ppm = 0 # do not move if we don't know ppm
ppm = 1
x, y = xy.x(), xy.y()
try:
bx, by = self.get_beam_mark_on_camera_xy()
@@ -1055,12 +1063,13 @@ class Main(QMainWindow, Ui_MainWindow):
bx, by = 500, 500
dx = (x - bx) / ppm
dy = -(y - by) / ppm
fx = self.tweakers["fast_x"].motor.get_position()
fy = self.tweakers["fast_y"].motor.get_position()
fx += dx
fy += dy
try:
fx = self.tweakers["fast_x"].motor.get_position()
fy = self.tweakers["fast_y"].motor.get_position()
fx += dx
fy += dy
except:
fx=fy=0
self._lb_coords.setText(
"\u23FA{:>}:{:>6.0f} {:<.0f}"
@@ -1073,10 +1082,11 @@ class Main(QMainWindow, Ui_MainWindow):
"PPM", ppm,
"Distance to beam", 1000 * dx, 1000 * dy,
"Stage", fx, fy,
)
)
)
def mouse_click_event(self, event):
app=QApplication.instance()
fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers()
_log.debug("{}".format(event))
_log.debug(" pos {}".format(event.pos()))
@@ -1090,13 +1100,13 @@ class Main(QMainWindow, Ui_MainWindow):
event.ignore()
return
pos = event.scenePos()
zoom_level = qoptic_zoom.get_position()
zoom_level = app._zoom.get()
_log.debug(" zoom {}".format(zoom_level))
try:
ppm = self.ppm_fitter(zoom_level)
except:
ppm = 0 # do not move if we don't know ppm
ppm = 1 # 0: do not move if we don't know ppm
dblclick = event.double()
shft = Qt.ShiftModifier & event.modifiers()
ctrl = Qt.ControlModifier & event.modifiers()
@@ -1110,11 +1120,11 @@ class Main(QMainWindow, Ui_MainWindow):
pixel_dist = np.linalg.norm(a - b)
_log.debug("distance to beam: {:.1f} pixels".format(pixel_dist))
omega = omega_motor.motor.get_position()
omega = omega_motor.get_position()
_log.debug("omega at {:.03f} degrees".format(omega))
fx = fx_motor.motor.get_position()
fy = fy_motor.motor.get_position()
fx = fx_motor.get_position()
fy = fy_motor.get_position()
dx = (x - bx) / ppm
dy = -(y - by) / ppm
fx += dx
@@ -1210,7 +1220,8 @@ class Main(QMainWindow, Ui_MainWindow):
pass
def get_beam_mark_on_camera_xy(self):
z = qoptic_zoom.get_position()
app=QApplication.instance()
z = app._zoom.get()
try:
bx = self.beamx_fitter(z)
by = self.beamy_fitter(z)
@@ -1250,6 +1261,7 @@ class Main(QMainWindow, Ui_MainWindow):
self._escape_current_state = "ManualSampleExchange"
def escape_goToSampleAlignment(self):
app=QApplication.instance()
self._escape_current_state = "busy"
steps = [
@@ -1258,7 +1270,7 @@ class Main(QMainWindow, Ui_MainWindow):
]
if option(CRYOJET_MOTION_ENABLED):
steps.extend([lambda: self.move_cryojet_nozzle("in")])
steps.extend([lambda: self.move_post_tube("out"), lambda: backlight.move("in")])
steps.extend([lambda: self.move_post_tube("out"), lambda: app._backlight.move("in")])
self.escape_run_steps(steps, "Transitioning to Sample Alignment")
self._escape_current_state = "SampleAlignment"
@@ -1435,6 +1447,9 @@ class Main(QMainWindow, Ui_MainWindow):
dx = settings.value("collimator/dx", 1e10, type=float)
dy = settings.value("collimator/dy", 1e10, type=float)
_log.info('Skip Zac''s orig. code')
return
# ZAC: orig. code
if 1e9 < x_pos + y_pos + dx + dy:
raise IncompleteConfiguration("COLLIMATOR configuration is incomplete!")
@@ -1704,6 +1719,9 @@ class Main(QMainWindow, Ui_MainWindow):
)
def move_post_tube(self, dir):
_log.info('Skip Zac''s orig. code')
return
# ZAC: orig. code
try:
tandem_twv = float(self._post_tandem_tweak_val.text())
except:
@@ -1809,11 +1827,19 @@ class Main(QMainWindow, Ui_MainWindow):
dsy.move_relative(-tandem_twv)
def get_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None, **kwargs):
app = QApplication.instance()
sim=app._args.sim
if mtype == "epics_motor":
m = MotorTweak()
if sim&0x10:
m=SimMotorTweak()
else:
m = MotorTweak()
else:
m = SmaractMotorTweak()
#m.connect_motor(pv, label, **kwargs) #ZAC: orig. code
if sim&0x20:
m=SimMotorTweak()
else:
m = SmaractMotorTweak()
m.connect_motor(pv, label, **kwargs)
self.tweakers[alias] = m
return m
@@ -1825,7 +1851,7 @@ class Main(QMainWindow, Ui_MainWindow):
else:
m = SmaractMotorTweak()
layout.addWidget(m)
#m.connect_motor(pv, label)#ZAC: orig. code
m.connect_motor(pv, label)
self.tweakers[alias] = m
def done_sliding(self):
@@ -3217,6 +3243,7 @@ def main():
# set app icon
app = QApplication(sys.argv)
app._args=args
if args.sim&0x01:
app._backlight = backlight.Backlight(None)
@@ -3232,7 +3259,7 @@ def main():
app._zoom = zoom.QopticZoom()
if args.sim&0x08:
app._camera = camera.epics_cam(None)
app._camera.sim_gen(mode=0)
app._camera.sim_gen(mode=1)
app._camera.run()
else:
app._camera = camera.epics_cam()