From 079c9dbc4cfb12e18dcea69c7e889f96fa798f65 Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Fri, 15 Jul 2022 16:17:13 +0200 Subject: [PATCH] reindent and some image tweeks --- app_config.py | 30 +- camera.py | 147 +- epics_widgets/MotorTweak.py | 574 ++-- swissmx.py | 5960 ++++++++++++++++++----------------- 4 files changed, 3395 insertions(+), 3316 deletions(-) diff --git a/app_config.py b/app_config.py index 6e370f1..19618b9 100644 --- a/app_config.py +++ b/app_config.py @@ -20,32 +20,32 @@ simulated = appsconf.get("simulate", False) logger.info(f"configuring for endstation: {endstation.upper()}") if simulated: - logger.warning("SIMULATION is ACTIVE") + logger.warning("SIMULATION is ACTIVE") css_file = inst_folder / "swissmx.css" def font(name: str) -> str: - p = Path(__file__).absolute().parent / "fonts" / name - return str(p) + p = Path(__file__).absolute().parent / "fonts" / name + return str(p) def logo(size: int = 0) -> str: - p = Path(__file__).absolute().parent / "logos" / "logo.png" - if size: - p = Path(__file__).absolute().parent / "logos" / f"tell_logo_{size}x{size}.png" - return str(p) + p = Path(__file__).absolute().parent / "logos" / "logo.png" + if size: + p = Path(__file__).absolute().parent / "logos" / f"tell_logo_{size}x{size}.png" + return str(p) def option(key: str) -> bool: - try: - return settings.value(key, type=bool) - except: - logger.error(f"option {key} not known") - return False + try: + return settings.value(key, type=bool) + except: + logger.error(f"option {key} not known") + return False def toggle_option(key: str): - v = settings.value(key, type=bool) - settings.setValue(key, not v) - settings.sync() + v = settings.value(key, type=bool) + settings.setValue(key, not v) + settings.sync() diff --git a/camera.py b/camera.py index 8c0ef40..943b4ac 100755 --- a/camera.py +++ b/camera.py @@ -24,6 +24,20 @@ Best regards Helge """ +# In [2]: np.array(range(20)) +# Out[2]: +# array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, +# 17, 18, 19]) + +# In [3]: np.array(range(20)).reshape(4,5) +# Out[3]: +# array([[ 0, 1, 2, 3, 4], +# [ 5, 6, 7, 8, 9], +# [10, 11, 12, 13, 14], +# [15, 16, 17, 18, 19]]) + +# shape is (imgidx,h,w) w is the fast counting index + import logging _log = logging.getLogger(__name__) @@ -99,7 +113,7 @@ class epics_cam(object): imgSeq=self._sim['imgSeq'] idx=self._sim['imgIdx'] self._sim['imgIdx']=(idx + 1) % imgSeq.shape[0] - _log.info('simulated idx:{}'.format(idx)) + #_log.debug('simulated idx:{}'.format(idx)) self.pic=pic=imgSeq[idx] return pic try: @@ -200,30 +214,54 @@ class epics_cam(object): def sim_gen(self,sz=(1500,1000),t=100,mode=0): 'generate simulation data' - _log.info('generate simulation images, mode:{}...'.format(mode)) - w,h=sz - self._imgSeq=imgSeq=np.ndarray(shape=(t,h,w),dtype=np.uint16) - x = np.linspace(-5, 5, w) - y = np.linspace(-5, 5, h) - # full coordinate arrays - xx, yy = np.meshgrid(x, y) + if mode==0: + _log.info('generate {} pulsing wases simulation images, mode:{}...'.format(t,mode)) + w,h=sz + imgSeq=np.ndarray(shape=(t,h,w),dtype=np.uint16) + x = np.linspace(-5, 5, w) + y = np.linspace(-5, 5, h) + # full coordinate arrays + xx, yy = np.meshgrid(x, y) + + for i in range(t): + #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+.1*i)**2 + np.sin(yy+.01*i)**2)#+xx*t+yy*t) + #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+.1*i)**2 + np.sin((1+.1*np.sin(.2*i))*yy+.001*i**2)**2)#+xx*t+yy*t) + #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+2*np.sin(i/t*2*np.pi))**2 + np.sin(yy)**2) + px=2*np.sin(i/t*2*np.pi) + fx=1 + py=2*np.sin(i/t*2*np.pi) + fy=1+.3*np.sin(i/t*2*np.pi*2) + imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx*fx+px)**2 + np.sin(yy*fy+py)**2) + #np.random.bytes(100) + wr=w//4 + hr=h//4 + imgSeq[:,0:hr,0:wr]+=np.random.randint(0,100,(t,hr,wr),dtype=np.uint16) + elif mode==1: + import glob,PIL.Image + path='/home/zamofing_t/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python/SwissMX/simCamImg/*.png' + _log.info('generate simulation images:{}...'.format(path)) + glb=glob.glob(path) + img = PIL.Image.open(glb[0]) + sz=img.size # (w,h) + imgSeq=np.ndarray(shape=(len(glb),sz[1],sz[0]),dtype=np.uint8) # shape is (n,h,w) + for i,fn in enumerate(glb): + img=PIL.Image.open(fn) + assert(img.mode=='L') # 8 bit grayscale + assert(sz==img.size) + imgSeq[i,:,:]=np.array(img.getdata()).reshape(sz[::-1]) + f=np.array(((0,0,0,0,0), + (0,1,1,1,0), + (0,1,0,0,0), + (0,1,1,0,0), + (0,1,0,0,0), + (0,0,0,0,0),),imgSeq.dtype) + imgSeq[i,0:6,0:5]=f*255 + + - for i in range(t): - #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+.1*i)**2 + np.sin(yy+.01*i)**2)#+xx*t+yy*t) - #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+.1*i)**2 + np.sin((1+.1*np.sin(.2*i))*yy+.001*i**2)**2)#+xx*t+yy*t) - #imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx+2*np.sin(i/t*2*np.pi))**2 + np.sin(yy)**2) - px=2*np.sin(i/t*2*np.pi) - fx=1 - py=2*np.sin(i/t*2*np.pi) - fy=1+.3*np.sin(i/t*2*np.pi*2) - imgSeq[i,:,:] = 100*np.sqrt(np.sin(xx*fx+px)**2 + np.sin(yy*fy+py)**2) - #np.random.bytes(100) - wr=w//4 - hr=h//4 - imgSeq[:,0:hr,0:wr]+=np.random.randint(0,100,(t,hr,wr),dtype=np.uint16) self._sim['imgSeq']=imgSeq self._sim['imgIdx']=0 - _log.info('dome') + _log.info('done-> shape:{} dtype:{}'.format(imgSeq.shape,imgSeq.dtype)) def set_transformations(self,*args): _log.error('OLD FUNCTION NOT IMPLEMENTED {}'.format(args)) @@ -232,7 +270,9 @@ class epics_cam(object): 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 ') + logging.basicConfig(level=logging.DEBUG,format='%(name)s:%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + logging.getLogger('PIL').setLevel(logging.INFO) + def default_app_open(file): if platform.system() == 'Darwin': # macOS @@ -248,6 +288,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--ui", "-u", help="qt test", type=int, default=0) parser.add_argument("--sim", "-s", help="simulation mode", type=int, default=None) + parser.add_argument("--delay", "-d", help="delay in simulation mode", type=float, default=None) parser.add_argument("--prefix","-p",help="PV prefix for images: default=%(default)s",type=str,default="SARES30-CAMS156-SMX-OAV",) args = parser.parse_args() @@ -328,7 +369,11 @@ if __name__ == "__main__": (0,1,0,0,0), (0,0,0,0,0),),pic.dtype) pic[0:6,0:5]=f*pic.max() - imv.setImage(pic, autoRange=False, autoLevels=False) + if args.ui==1: + img.setImage(pic) + else: + imv.setImage(pic, autoRange=False, autoLevels=False) + def new_frame_sim_cb(self,arl=False): imgSeq =self._sim['imgSeq'] @@ -338,9 +383,15 @@ if __name__ == "__main__": self._sim['imgIdx']=(idx+1) % imgSeq.shape[0] #_log.info('simulated idx:{}'.format(idx)) pic = imgSeq[idx] - imv.setImage(pic, autoRange=arl, autoLevels=arl) + if args.ui==1: + img.setImage(pic) + else: + imv.setImage(pic, autoRange=arl, autoLevels=arl) - QtCore.QTimer.singleShot(1, self.new_frame_sim_cb) + if args.delay: + QtCore.QTimer.singleShot(int(1000*args.delay), self.new_frame_sim_cb) + else: + QtCore.QTimer.singleShot(0, self.new_frame_sim_cb) now = ptime.time() fps2 = 1.0 / (now - udt) self._sim['updateTime'] = now @@ -358,13 +409,34 @@ if __name__ == "__main__": app = QtGui.QApplication([]) - ## Create window with ImageView widget - win = QtGui.QMainWindow() - win.resize(800,800) - imv = pg.ImageView() - win.setCentralWidget(imv) - win.show() - win.setWindowTitle('pyqtgraph example: ImageView') + if args.ui==1: + win = pg.GraphicsLayoutWidget() + win.show() ## show widget alone in its own window + win.setWindowTitle('pyqtgraph example: ImageItem') + view = win.addViewBox(invertY=True) + + ## lock the aspect ratio so pixels are always square + view.setAspectLocked(True) + + ## Create image item https://pyqtgraph.readthedocs.io/en/latest/graphicsItems/imageitem.html + img = pg.ImageItem(border='g') + #tr = QtGui.QTransform() # prepare ImageItem transformation: + #tr.scale(6.0, 3.0) # scale horizontal and vertical axes + #tr.translate(-1.5, -1.5) # move 3x3 image to locate center at axis origin + #img.setTransform(tr) # assign transform + view.addItem(img) + + ## Set initial view bounds + view.setRange(QtCore.QRectF(0, 0, 600, 600)) + else: + ## Create window with ImageView widget + win = QtGui.QMainWindow() + win.resize(800,800) + imv = pg.ImageView() + win.setCentralWidget(imv) + win.show() + win.setWindowTitle('pyqtgraph example: ImageView') + ## Display the data and assign each frame a time value from 1.0 to 3.0 cam = UIcamera(prefix=args.prefix) @@ -383,10 +455,11 @@ if __name__ == "__main__": cam._sim['updateTime'] = ptime.time() cam.new_frame_sim_cb(arl=True) - ## Set a custom color map - colors = [(0, 0, 0),(45, 5, 61),(84, 42, 55),(150, 87, 60),(208, 171, 141),(255, 255, 255)] - cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors) - imv.setColorMap(cmap) + if args.ui==2: + ## Set a custom color map + colors = [(0, 0, 0),(45, 5, 61),(84, 42, 55),(150, 87, 60),(208, 171, 141),(255, 255, 255)] + cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors) + imv.setColorMap(cmap) ## Start Qt event loop unless running in interactive mode. if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): diff --git a/epics_widgets/MotorTweak.py b/epics_widgets/MotorTweak.py index 571677c..c5407b1 100644 --- a/epics_widgets/MotorTweak.py +++ b/epics_widgets/MotorTweak.py @@ -1,5 +1,7 @@ -import math import logging +_log = logging.getLogger(__name__) + +import math from time import sleep from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QPainter, QBrush, QColor, QPainterPath, QPen, QDoubleValidator @@ -11,8 +13,6 @@ from epics.ca import pend_event from app_utils import DeltatauMotorStatus, assert_motor_positions Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui') -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) SPMG_STOP = 0 SPMG_PAUSE = 1 SPMG_MOVE = 2 @@ -20,336 +20,340 @@ SPMG_GO = 3 class MotorTweak(QWidget, Ui_MotorTweak): - event_val = pyqtSignal(str, dict) - event_readback = pyqtSignal(str, float, 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) + event_val = pyqtSignal(str, dict) + event_readback = pyqtSignal(str, float, 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(MotorTweak, 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': '{short_name} {{rbv:.{precision}f}} {units}', - 'small': '{short_name} {{rbv:.{precision}f}} {units}', - '2 lines': '{short_name}
{{rbv:.{precision}f}} {units}', - 'busy': '{short_name} {{rbv:.{precision}f}} {units}' - } - self._templates = {} + def __init__(self, parent=None): + super(MotorTweak, 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': '{short_name} {{rbv:.{precision}f}} {units}', + 'small': '{short_name} {{rbv:.{precision}f}} {units}', + '2 lines': '{short_name}
{{rbv:.{precision}f}} {units}', + 'busy': '{short_name} {{rbv:.{precision}f}} {units}' + } + self._templates = {} - def connect_motor(self, motor_base, short_name=None, **kwargs): - m = Motor(motor_base) - m.get_position() - self._ignore_limits = m.HLM == m.LLM # if both high/low limits are equal they are meaningless - self.motor = m - if not short_name: - short_name = m.description - self.short_name = short_name - self._pvname = motor_base + def connect_motor(self, motor_base, short_name=None, **kwargs): + m = Motor(motor_base) + m.get_position() + self._ignore_limits = m.HLM == m.LLM # if both high/low limits are equal they are meaningless + self.motor = m + if not short_name: + short_name = m.description + self.short_name = short_name + self._pvname = motor_base - for attr in ['RTYP', 'JVEL', 'HLS', 'LLS', 'TWV', 'RBV', 'VAL', 'LVIO', 'HLM', 'LLM']: - # logger.debug('connecting to field {}'.format(attr)) - m.PV(attr, connect=True) + for attr in ['RTYP', 'JVEL', 'HLS', 'LLS', 'TWV', 'RBV', 'VAL', 'LVIO', 'HLM', 'LLM']: + # _log.debug('connecting to field {}'.format(attr)) + m.PV(attr, connect=True) - self.set_motor_validator() - self._drive_val.setText(m.get('VAL', as_string=True)) - self._drive_val.returnPressed.connect(self.move_motor_to_position) + self.set_motor_validator() + self._drive_val.setText(m.get('VAL', as_string=True)) + self._drive_val.returnPressed.connect(self.move_motor_to_position) - tweak_min = kwargs.get("tweak_min", 0.0001) - tweak_max = kwargs.get("tweak_max", 10.0) - self._tweak_val.setText(m.get('TWV', as_string=True)) - self._tweak_val.setValidator(QDoubleValidator(tweak_min, tweak_max, 4, self._tweak_val)) - self._tweak_val.editingFinished.connect(lambda m=m: m.PV('TWV').put(self._tweak_val.text())) + tweak_min = kwargs.get("tweak_min", 0.0001) + tweak_max = kwargs.get("tweak_max", 10.0) + self._tweak_val.setText(m.get('TWV', as_string=True)) + self._tweak_val.setValidator(QDoubleValidator(tweak_min, tweak_max, 4, self._tweak_val)) + self._tweak_val.editingFinished.connect(lambda m=m: m.PV('TWV').put(self._tweak_val.text())) - self.jog_reverse.hide() - self.jog_forward.hide() - # self.jog_forward.pressed.connect(lambda: self.jog('forward', True)) - # self.jog_reverse.pressed.connect(lambda: self.jog('reverse', True)) - # self.jog_forward.released.connect(lambda: self.jog('forward', False)) - # self.jog_reverse.released.connect(lambda: self.jog('reverse', False)) + self.jog_reverse.hide() + self.jog_forward.hide() + # self.jog_forward.pressed.connect(lambda: self.jog('forward', True)) + # self.jog_reverse.pressed.connect(lambda: self.jog('reverse', True)) + # self.jog_forward.released.connect(lambda: self.jog('forward', False)) + # self.jog_reverse.released.connect(lambda: self.jog('reverse', False)) - self.tweak_forward.clicked.connect(lambda m: self.motor.tweak('forward')) - self.tweak_reverse.clicked.connect(lambda m: self.motor.tweak('reverse')) + self.tweak_forward.clicked.connect(lambda m: self.motor.tweak('forward')) + self.tweak_reverse.clicked.connect(lambda m: self.motor.tweak('reverse')) - self.bind_wheel() + self.bind_wheel() - self.update_label_template() - m.add_callback('VAL', self.set_val) - m.add_callback('RBV', self.update_label) - m.set_callback('HLS', self.update_label) - m.set_callback('LLS', self.update_label) + self.update_label_template() + m.add_callback('VAL', self.set_val) + m.add_callback('RBV', self.update_label) + m.set_callback('HLS', self.update_label) + m.set_callback('LLS', self.update_label) - m.set_callback('VAL', self.emit_signals, {'source_field': 'VAL'}) - m.set_callback('RBV', self.emit_signals, {'source_field': 'RBV'}) - m.set_callback('HLS', self.emit_signals, {'source_field': 'HLS'}) - m.set_callback('LLS', self.emit_signals, {'source_field': 'LLS'}) - m.set_callback('LVIO', self.emit_signals, {'source_field': 'LVIO'}) - m.set_callback('STAT', self.emit_signals, {'source_field': 'STAT'}) + m.set_callback('VAL', self.emit_signals, {'source_field': 'VAL'}) + m.set_callback('RBV', self.emit_signals, {'source_field': 'RBV'}) + m.set_callback('HLS', self.emit_signals, {'source_field': 'HLS'}) + m.set_callback('LLS', self.emit_signals, {'source_field': 'LLS'}) + m.set_callback('LVIO', self.emit_signals, {'source_field': 'LVIO'}) + m.set_callback('STAT', self.emit_signals, {'source_field': 'STAT'}) - m.set_callback('HLM', self.set_motor_validator) + m.set_callback('HLM', self.set_motor_validator) - def set_val(self, **kw): - v = kw['char_value'] - logger.debug('updating VAL = {}'.format(v)) - self._drive_val.setText(v) + def set_val(self, **kw): + v = kw['char_value'] + _log.debug('updating VAL = {}'.format(v)) + self._drive_val.setText(v) - def set_motor_validator(self, **kwargs): - m = self.motor - lineedit = self._drive_val - min, max = m.PV('LLM').get(), m.PV('HLM').get() - if min == max: - min = -1e6 - max = 1e6 - lineedit.setValidator(QDoubleValidator(min, max, m.PV('PREC').get(), lineedit)) + def set_motor_validator(self, **kwargs): + m = self.motor + lineedit = self._drive_val + min, max = m.PV('LLM').get(), m.PV('HLM').get() + if min == max: + min = -1e6 + max = 1e6 + lineedit.setValidator(QDoubleValidator(min, max, m.PV('PREC').get(), lineedit)) - def move_relative(self, dist): - self.motor.move(dist, ignore_limits=True, relative=True) + def move_relative(self, dist): + self.motor.move(dist, ignore_limits=True, relative=True) - def is_done(self): - self.motor.refresh() - return 1 == self.motor.done_moving + def is_done(self): + self.motor.refresh() + return 1 == self.motor.done_moving - def get_position(self): - return self.motor.get_position(readback=True) + def get_position(self): + return self.motor.get_position(readback=True) - def is_homed(self): - self.motor.refresh() - status = DeltatauMotorStatus(self.motor.motor_status) - return bool(DeltatauMotorStatus.MOTOR_HAS_BEEN_HOMED & status) + def is_homed(self): + self.motor.refresh() + status = DeltatauMotorStatus(self.motor.motor_status) + return bool(DeltatauMotorStatus.MOTOR_HAS_BEEN_HOMED & status) - def move_motor_to_position(self, drive=None, wait=False, assert_position=False): - if drive is None: - drive = float(self._drive_val.text()) - if assert_position: - wait=True - self.motor.move(drive, wait=wait, ignore_limits=True, relative=False) - if assert_position: - assert_motor_positions([(self.motor, drive, 0.1)], timeout=1) + def move_motor_to_position(self, drive=None, wait=False, assert_position=False): + if drive is None: + drive = float(self._drive_val.text()) + if assert_position: + wait=True + self.motor.move(drive, wait=wait, ignore_limits=True, relative=False) + if assert_position: + assert_motor_positions([(self.motor, drive, 0.1)], timeout=1) - def emit_signals(self, **kw): - ''' - :param kw: info about the channel { - 'access': 'read-only', - 'char_value': '-0.105', - 'chid': 36984304, - 'count': 1, - 'enum_strs': None, - 'ftype': 20, - 'host': 'SAR-CPPM-EXPMX1.psi.ch:5064', - 'lower_alarm_limit': None, - 'lower_ctrl_limit': 0.0, - 'lower_disp_limit': 0.0, - 'lower_warning_limit': None, - 'motor_field': 'RBV', - 'nelm': 1, - 'precision': 3, - 'pvname': 'SAR-EXPMX:MOT_ROT_Y.RBV', - 'read_access': True, - 'severity': 0, - 'source_field': 'RBV', - 'status': 0, - 'timestamp': 1522314072.878331, - 'type': 'time_double', - 'typefull': 'time_double', - 'units': b'deg', - 'upper_alarm_limit': None, - 'upper_ctrl_limit': 0.0, - 'upper_disp_limit': 0.0, - 'upper_warning_limit': None, - 'value': -0.105, - 'write_access': False} + def emit_signals(self, **kw): + ''' + :param kw: info about the channel { + 'access': 'read-only', + 'char_value': '-0.105', + 'chid': 36984304, + 'count': 1, + 'enum_strs': None, + 'ftype': 20, + 'host': 'SAR-CPPM-EXPMX1.psi.ch:5064', + 'lower_alarm_limit': None, + 'lower_ctrl_limit': 0.0, + 'lower_disp_limit': 0.0, + 'lower_warning_limit': None, + 'motor_field': 'RBV', + 'nelm': 1, + 'precision': 3, + 'pvname': 'SAR-EXPMX:MOT_ROT_Y.RBV', + 'read_access': True, + 'severity': 0, + 'source_field': 'RBV', + 'status': 0, + 'timestamp': 1522314072.878331, + 'type': 'time_double', + 'typefull': 'time_double', + 'units': b'deg', + 'upper_alarm_limit': None, + 'upper_ctrl_limit': 0.0, + 'upper_disp_limit': 0.0, + 'upper_warning_limit': None, + 'value': -0.105, + 'write_access': False} - :return: - ''' - field = kw['motor_field'] - src = kw['source_field'] - kw['alias'] = self.short_name - if field != src: - return - if field == 'VAL': - self.event_val.emit(self._pvname, kw) - elif field == 'RBV': - self.event_readback.emit(kw['alias'], kw['value'], 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) + :return: + ''' + field = kw['motor_field'] + src = kw['source_field'] + kw['alias'] = self.short_name + if field != src: + return + if field == 'VAL': + self.event_val.emit(self._pvname, kw) + elif field == 'RBV': + self.event_readback.emit(kw['alias'], kw['value'], 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 update_label(self, **kwargs): - m = self.motor - self.label.setText(self._templates[self._label_style].format(rbv=m.readback)) - self.jog_forward.setToolTip('jog forward at {:.3f} {}/s'.format(m.jog_speed, m.units)) - self.jog_reverse.setToolTip('jog reverse at {:.3f} {}/s'.format(m.jog_speed, m.units)) - self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(m.tweak_val, m.units)) - self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(m.tweak_val, m.units)) + def update_label(self, **kwargs): + m = self.motor + self.label.setText(self._templates[self._label_style].format(rbv=m.readback)) + self.jog_forward.setToolTip('jog forward at {:.3f} {}/s'.format(m.jog_speed, m.units)) + self.jog_reverse.setToolTip('jog reverse at {:.3f} {}/s'.format(m.jog_speed, m.units)) + self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(m.tweak_val, m.units)) + self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(m.tweak_val, m.units)) - def update_jog_speed(self, event): - m = self.motor - speed = m.jog_speed - sign = math.copysign(1, event.angleDelta().y()) - m.jog_speed = speed + (sign * 0.1 * speed) - self.jog_forward.setToolTip('jog forward at {:.3f} {}/s'.format(m.jog_speed, m.units)) - self.jog_reverse.setToolTip('jog reverse at {:.3f} {}/s'.format(m.jog_speed, m.units)) + def update_jog_speed(self, event): + m = self.motor + speed = m.jog_speed + sign = math.copysign(1, event.angleDelta().y()) + m.jog_speed = speed + (sign * 0.1 * speed) + self.jog_forward.setToolTip('jog forward at {:.3f} {}/s'.format(m.jog_speed, m.units)) + self.jog_reverse.setToolTip('jog reverse at {:.3f} {}/s'.format(m.jog_speed, m.units)) - def tweak_event(self, event): - m = self.motor - sign = event.angleDelta().y() - if sign < 0: - m.tweak_reverse = 1 - else: - m.tweak_forward = 1 + def tweak_event(self, event): + m = self.motor + sign = event.angleDelta().y() + if sign < 0: + m.tweak_reverse = 1 + else: + m.tweak_forward = 1 - def bind_wheel(self): - self.tweak_forward.wheelEvent = self.tweak_event - self.tweak_reverse.wheelEvent = self.tweak_event + def bind_wheel(self): + self.tweak_forward.wheelEvent = self.tweak_event + self.tweak_reverse.wheelEvent = self.tweak_event - def jog(self, direction, enable): - m = self.motor - if 'forw' in direction: - m.jog_forward = int(enable) - else: - m.jog_reverse = int(enable) + def jog(self, direction, enable): + m = self.motor + if 'forw' in direction: + m.jog_forward = int(enable) + else: + m.jog_reverse = int(enable) - def contextMenuEvent(self, event): - m = self.motor - menu = QMenu(self) - menu.setTitle(self.short_name) + def contextMenuEvent(self, event): + m = self.motor + menu = QMenu(self) + menu.setTitle(self.short_name) - lockmotor = QAction('lock motor', menu, checkable=True) - lockmotor.setChecked(self._locked) - menu.addAction(lockmotor) + 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) + 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) + 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") - changejogspeedAction = menu.addAction("Jog speed {:.3f} {}/s".format(m.jog_speed, m.units)) - changetweakstepAction = menu.addAction("Tweak step {:.3f} {}".format(m.tweak_val, m.units)) + stopmotorAction = QAction('Stopped', menu, checkable=True) + stopmotorAction.setChecked(SPMG_STOP == m.stop_go) + menu.addAction(stopmotorAction) + changeprecAction = menu.addAction("Change Precision") + changejogspeedAction = menu.addAction("Jog speed {:.3f} {}/s".format(m.jog_speed, m.units)) + changetweakstepAction = menu.addAction("Tweak step {:.3f} {}".format(m.tweak_val, m.units)) - tozeroAction = menu.addAction("move to Zero") + 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: - name = m.NAME - prec = m.PREC - 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.motor.put('PREC', prec, wait=True) + 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: + name = m.NAME + prec = m.PREC + msg = 'Precision for motor {}'.format(name) + _log.debug('prec before %d', prec) + prec, ok = QInputDialog.getInt(self, msg, msg, prec, 0, 10) + _log.debug('prec after (%d) %d', ok, prec) + if ok: + self.motor.put('PREC', prec, wait=True) - elif action == changejogspeedAction: - name = m.NAME - speed = m.jog_speed - msg = 'Jog speed for motor {}'.format(name) - speed, ok = QInputDialog.getDouble(self, msg, msg, speed, 0.01, m.slew_speed, m.precision) - if ok: - self.motor.put('JVEL', speed, wait=True) + elif action == changejogspeedAction: + name = m.NAME + speed = m.jog_speed + msg = 'Jog speed for motor {}'.format(name) + speed, ok = QInputDialog.getDouble(self, msg, msg, speed, 0.01, m.slew_speed, m.precision) + if ok: + self.motor.put('JVEL', speed, wait=True) - elif action == changetweakstepAction: - name = m.NAME - tv = m.tweak_val - msg = 'Tweak step for motor {} at {} {}'.format(name, tv, m.units) - tv, ok = QInputDialog.getDouble(self, msg, msg, tv, pow(10, -m.precision), m.slew_speed, m.precision) - if ok: - self.motor.put('TWV', tv, wait=True) + elif action == changetweakstepAction: + name = m.NAME + tv = m.tweak_val + msg = 'Tweak step for motor {} at {} {}'.format(name, tv, m.units) + tv, ok = QInputDialog.getDouble(self, msg, msg, tv, pow(10, -m.precision), m.slew_speed, m.precision) + if ok: + self.motor.put('TWV', tv, wait=True) - elif action == tozeroAction: - m.move(0.0, ignore_limits=True) + elif action == tozeroAction: + m.move(0.0, ignore_limits=True) - elif action == stopmotorAction: - self.motor.stop_go = SPMG_STOP if m.stop_go == SPMG_GO else SPMG_GO + elif action == stopmotorAction: + self.motor.stop_go = SPMG_STOP if m.stop_go == SPMG_GO else SPMG_GO - elif action == autolabelsAction: - self._auto_labels = not self._auto_labels + elif action == autolabelsAction: + self._auto_labels = not self._auto_labels - elif action == wheel_tweaks: - self._wheel_tweaks = not self._wheel_tweaks - self.bind_wheel() + elif action == wheel_tweaks: + self._wheel_tweaks = not self._wheel_tweaks + self.bind_wheel() - self.update_label_template() + self.update_label_template() - def resizeEvent(self, event): - return # FIXME disable for the time being - if not self._auto_labels: - return + def resizeEvent(self, event): + return # FIXME disable for the time being + if not self._auto_labels: + return - w, h = event.size().width(), event.size().height() - if w < 400: - self.jog_reverse.hide() - self.jog_forward.hide() - else: - self.jog_reverse.show() - self.jog_forward.show() - self.update_label() + w, h = event.size().width(), event.size().height() + if w < 400: + self.jog_reverse.hide() + self.jog_forward.hide() + else: + self.jog_reverse.show() + self.jog_forward.show() + self.update_label() - def update_label_template(self): - m = self.motor - source = self._templates_source - target = self._templates + def update_label_template(self): + m = self.motor + source = self._templates_source + target = self._templates - for k in source: - target[k] = source[k].format( - short_name=self.short_name, - precision=m.PREC, - units=m.units) - self.label.setText(target[self._label_style].format(rbv=m.readback)) + for k in source: + target[k] = source[k].format( + short_name=self.short_name, + precision=m.PREC, + units=m.units) + self.label.setText(target[self._label_style].format(rbv=m.readback)) - def paintEvent(self, e): - qp = QPainter() - qp.begin(self) - qp.setRenderHint(QPainter.Antialiasing) - self._draw_limits(qp) - qp.end() + def paintEvent(self, e): + qp = QPainter() + qp.begin(self) + qp.setRenderHint(QPainter.Antialiasing) + self._draw_limits(qp) + qp.end() - def _draw_limits(self, qp): - m = self.motor - 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) + def _draw_limits(self, qp): + try: + m = self.motor + except AttributeError: + _log.warning('Motor not connected') + return + 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) diff --git a/swissmx.py b/swissmx.py index 5d35538..dba64fe 100755 --- a/swissmx.py +++ b/swissmx.py @@ -93,9 +93,9 @@ from PyQt5 import QtCore, QtGui from PyQt5.QtCore import Qt, pyqtSlot, QSize, QRegExp, pyqtSignal, QObject, QThread from PyQt5.QtGui import QKeySequence, QPixmap, QRegExpValidator from PyQt5.QtWidgets import ( - QAction, QApplication, QDoubleSpinBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, - QMessageBox, QPlainTextEdit, QProgressBar, QProgressDialog, QPushButton, QShortcut, QSizePolicy, QSpinBox, - QSplashScreen, QTextBrowser, QToolBox, QVBoxLayout, QWidget,) + QAction, QApplication, QDoubleSpinBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, + QMessageBox, QPlainTextEdit, QProgressBar, QProgressDialog, QPushButton, QShortcut, QSizePolicy, QSpinBox, + QSplashScreen, QTextBrowser, QToolBox, QVBoxLayout, QWidget,) from PyQt5.uic import loadUiType from CustomROI import BeamMark @@ -155,3122 +155,3124 @@ import camera,backlight,zoom,illumination #sample_camera = camera.camera_server(basename=appsconf["microscope"]["sample_camera"]["pv_prefix"]) #ZAC: orig. code def print_transform(t): - m11 = t.m11() - m12 = t.m12() - m13 = t.m13() - m21 = t.m21() - m22 = t.m22() - m23 = t.m23() - m31 = t.m31() - m32 = t.m32() - m33 = t.m33() - print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format("Transform", m11, m12, m13)) - print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format(" ", m21, m22, m23)) - print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format(" ", m31, m32, m33)) + m11 = t.m11() + m12 = t.m12() + m13 = t.m13() + m21 = t.m21() + m22 = t.m22() + m23 = t.m23() + m31 = t.m31() + m32 = t.m32() + m33 = t.m33() + print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format("Transform", m11, m12, m13)) + print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format(" ", m21, m22, m23)) + print("{:20s}{:12.3f} {:12.3f} {:12.3f}".format(" ", m31, m32, m33)) def tdstamp(): - return time.strftime("%Y%m%dH%H%M%S") + return time.strftime("%Y%m%dH%H%M%S") def datestamp(): - return time.strftime("%Y%m%d") + return time.strftime("%Y%m%d") class AcquisitionAbortedException(Exception): - pass + pass class Sequencer(QObject): - finished = pyqtSignal() - timeoutExpired = pyqtSignal() - errorHappened = pyqtSignal(str) + finished = pyqtSignal() + timeoutExpired = pyqtSignal() + errorHappened = pyqtSignal(str) - def __init__(self, steps): - super().__init__() - self.steps = steps + def __init__(self, steps): + super().__init__() + self.steps = steps - def run_sequence(self): - num_steps = len(self.steps) - for n, step in enumerate(self.steps): - _log.info("runing step {}/{}".format(n, num_steps)) - step() - _log.info("emiting finished") - self.finished.emit() + def run_sequence(self): + num_steps = len(self.steps) + for n, step in enumerate(self.steps): + _log.info("runing step {}/{}".format(n, num_steps)) + step() + _log.info("emiting finished") + self.finished.emit() Ui_MainWindow, QMainWindow = loadUiType("swissmx.ui") class Main(QMainWindow, Ui_MainWindow): - pixelsPerMillimeter = pyqtSignal(float) - beamCameraCoordinatesChanged = pyqtSignal(float, float) - addGridRequest = pyqtSignal(float, float) - zoomChanged = pyqtSignal(float) - folderChanged = pyqtSignal(str) - prefixChanged = pyqtSignal(str) - projectChanged = pyqtSignal(str) - gridUpdated = pyqtSignal(int) # index in self._grids - gonioMoveRequest = pyqtSignal(float, float, float, float, float) - fast_x_position = pyqtSignal(float) - fast_y_position = pyqtSignal(float) - fast_dx_position = pyqtSignal(float) - fast_dy_position = pyqtSignal(float) - fiducialPositionSelected = pyqtSignal(float, float, float, float) # camx, camy, gonx, gony - appendPrelocatedPosition = pyqtSignal(float, float, float, float) - appendPrelocatedFiducial = pyqtSignal(bool, float, float, float, float) - increaseRunNumberRequest = pyqtSignal() - daqAborted = pyqtSignal() + pixelsPerMillimeter = pyqtSignal(float) + beamCameraCoordinatesChanged = pyqtSignal(float, float) + addGridRequest = pyqtSignal(float, float) + zoomChanged = pyqtSignal(float) + folderChanged = pyqtSignal(str) + prefixChanged = pyqtSignal(str) + projectChanged = pyqtSignal(str) + gridUpdated = pyqtSignal(int) # index in self._grids + gonioMoveRequest = pyqtSignal(float, float, float, float, float) + fast_x_position = pyqtSignal(float) + fast_y_position = pyqtSignal(float) + fast_dx_position = pyqtSignal(float) + fast_dy_position = pyqtSignal(float) + fiducialPositionSelected = pyqtSignal(float, float, float, float) # camx, camy, gonx, gony + appendPrelocatedPosition = pyqtSignal(float, float, float, float) + appendPrelocatedFiducial = pyqtSignal(bool, float, float, float, float) + increaseRunNumberRequest = pyqtSignal() + daqAborted = pyqtSignal() - def __init__(self,): - super(Main, self).__init__() - self.setupUi(self) + def __init__(self,): + super(Main, self).__init__() + self.setupUi(self) - if os.getenv("DEVELOPMENT_VERSION"): - title = "[DEVELOPMENT] SwissMX - Solid support scanning for FEL sources" - else: - title = "SwissMX - Solid support scanning for FEL sources" - self.setWindowTitle(title) + if os.getenv("DEVELOPMENT_VERSION"): + title = "[DEVELOPMENT] SwissMX - Solid support scanning for FEL sources" + else: + title = "SwissMX - Solid support scanning for FEL sources" + self.setWindowTitle(title) - self._do_quit = False - qtawesome.load_font("material", "MaterialIcons-Regular.ttf", "MaterialIcons-Regular.json", "fonts/",) + self._do_quit = False + qtawesome.load_font("material", "MaterialIcons-Regular.ttf", "MaterialIcons-Regular.json", "fonts/",) - QtGui.QFontDatabase.addApplicationFont("fonts/Inconsolata-Bold.ttf") - QtGui.QFontDatabase.addApplicationFont("fonts/Baloo-Regular.ttf") + QtGui.QFontDatabase.addApplicationFont("fonts/Inconsolata-Bold.ttf") + QtGui.QFontDatabase.addApplicationFont("fonts/Baloo-Regular.ttf") - self._pv_shutter = None # epics.PV('X06SA-ES-MD2:SHUTTER') - self._has_pulse_picker = False - self._at_x06sa = False - self._at_cristalina = True - self._at_lab_eh060 = False + self._pv_shutter = None # epics.PV('X06SA-ES-MD2:SHUTTER') + self._has_pulse_picker = False + self._at_x06sa = False + self._at_cristalina = True + self._at_lab_eh060 = False - self.init_settings() + self.init_settings() - # self.create_escape_toolbar() - self.tweakers = {} - self.setup_sliders() + # self.create_escape_toolbar() + self.tweakers = {} + self.setup_sliders() - self.glw = pg.GraphicsLayoutWidget() - self.microscope_page.setLayout(QVBoxLayout()) - self.microscope_page.layout().addWidget(self.glw) - self.glw.show() + self.glw = pg.GraphicsLayoutWidget() + self.microscope_page.setLayout(QVBoxLayout()) + self.microscope_page.layout().addWidget(self.glw) + self.glw.show() - self.vb = self.glw.addViewBox(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.setBackgroundColor((120, 90, 90)) - self.vb.addItem(self.img) + self.vb = self.glw.addViewBox(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.setBackgroundColor((120, 90, 90)) + self.vb.addItem(self.img) - self._escape_current_state = "Maintenance" - self._pin_mounting_offset = 0.0 + self._escape_current_state = "Maintenance" + self._pin_mounting_offset = 0.0 - self._mouse_tracking = False - self._rois = [] + self._mouse_tracking = False + self._rois = [] - # add a generic ellipse - # pen = QtGui.QPen(QColor(Qt.yellow)) - # pen.setWidth(5) - # ellipse1 = QtWidgets.QGraphicsEllipseItem(650, 700, 500, 300) - # ellipse1.setFlag(QGraphicsItem.ItemIsSelectable) - # ellipse1.setFlag(QGraphicsItem.ItemIsMovable) - # ellipse1.setPen(pen) - # self.vb.addItem(ellipse1) + # add a generic ellipse + # pen = QtGui.QPen(QColor(Qt.yellow)) + # pen.setWidth(5) + # ellipse1 = QtWidgets.QGraphicsEllipseItem(650, 700, 500, 300) + # ellipse1.setFlag(QGraphicsItem.ItemIsSelectable) + # ellipse1.setFlag(QGraphicsItem.ItemIsMovable) + # ellipse1.setPen(pen) + # self.vb.addItem(ellipse1) - # self.vb.addItem(Grid(pos=(650, 700), size=(100, 200), cell_size=(3, 4), parent=self)) - # self.vb.addItem(Retangulo(pos=(750, 700), size=(100, 200), pen=pg.fn.mkPen(color=[230, 150, 200], width=4.7))) - # self.vb.addItem(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True)) + # self.vb.addItem(Grid(pos=(650, 700), size=(100, 200), cell_size=(3, 4), parent=self)) + # self.vb.addItem(Retangulo(pos=(750, 700), size=(100, 200), pen=pg.fn.mkPen(color=[230, 150, 200], width=4.7))) + # self.vb.addItem(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True)) - # test - # x2 = np.linspace(-100, 100, 1000) - # data2 = np.sin(x2) / x2 - # # p8 = self.glw.addPlot(row=1, col=0, title="Region Selection") - # plitem = pg.PlotItem() - # plitem.plot(data2, pen=(255, 255, 255, 200)) - # # lr = pg.LinearRegionItem([400, 700]) - # # lr.setZValue(-10) - # self.vb.addItem(plitem) + # test + # x2 = np.linspace(-100, 100, 1000) + # data2 = np.sin(x2) / x2 + # # p8 = self.glw.addPlot(row=1, col=0, title="Region Selection") + # plitem = pg.PlotItem() + # plitem.plot(data2, pen=(255, 255, 255, 200)) + # # lr = pg.LinearRegionItem([400, 700]) + # # lr.setZValue(-10) + # self.vb.addItem(plitem) - self._message_critical_fault = QLabel(None) - self._message_critical_fault.setAccessibleName("device_fault") - self.statusbar.insertWidget(0, self._message_critical_fault) - self._lb_coords = QLabel(None) - self._lb_coords.setText("coords") - self.statusbar.addPermanentWidget(self._lb_coords) + self._message_critical_fault = QLabel(None) + self._message_critical_fault.setAccessibleName("device_fault") + self.statusbar.insertWidget(0, self._message_critical_fault) + self._lb_coords = QLabel(None) + self._lb_coords.setText("coords") + self.statusbar.addPermanentWidget(self._lb_coords) - self._fel_status = QLabel(None) - self._fel_status.setAccessibleName("fel_status_statusbar") - self.statusbar.addPermanentWidget(self._fel_status) - #swissfel.statusStringUpdated.connect(self._fel_status.setText) #ZAC: orig. code - self._status_task = QLabel(None) - self._status_task.setAccessibleName("status_task_label") - self.statusbar.addPermanentWidget(self._status_task) + self._fel_status = QLabel(None) + self._fel_status.setAccessibleName("fel_status_statusbar") + self.statusbar.addPermanentWidget(self._fel_status) + #swissfel.statusStringUpdated.connect(self._fel_status.setText) #ZAC: orig. code + self._status_task = QLabel(None) + self._status_task.setAccessibleName("status_task_label") + self.statusbar.addPermanentWidget(self._status_task) - self.add_beam_marker() - self._beam_markers = {} + self.add_beam_marker() + self._beam_markers = {} - # ppm calibration - self._zoom_to_ppm = {} - self._ppm_click = None + # ppm calibration + self._zoom_to_ppm = {} + self._ppm_click = None - self.load_stylesheet() + self.load_stylesheet() - self.folderChanged.connect(lambda t: print("folder now: {}".format(t))) # FIXME + self.folderChanged.connect(lambda t: print("folder now: {}".format(t))) # FIXME - self.init_actions() - self.prepare_microscope_page() - self.prepare_embl_gui() - self.prepare_left_tabs() - #self.update_beam_marker(qoptic_zoom.get_position()) #ZAC: orig. code - self._centerpiece_stack.setCurrentIndex(0) - self._centerpiece_stack.currentChanged.connect(self.center_piece_update) - self.init_validators() - self.init_settings_tracker() - self.wire_storage() + self.init_actions() + self.prepare_microscope_page() + #self.prepare_embl_gui() + self.prepare_left_tabs() + #self.update_beam_marker(qoptic_zoom.get_position()) #ZAC: orig. code + self._centerpiece_stack.setCurrentIndex(0) + self._centerpiece_stack.currentChanged.connect(self.center_piece_update) + self.init_validators() + self.init_settings_tracker() + self.wire_storage() - #self.create_helical_widgets() #ZAC: orig. code + #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) - self._tabs_daq_methods.currentChanged.connect(self.switch_task) - self.switch_task() + 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) + self._tabs_daq_methods.currentChanged.connect(self.switch_task) + self.switch_task() - def create_helical_widgets(self): - tbox = self._helical_tablebox - htab = self._helical_scan_table = HelicalTableWidget() - htab.gonioMoveRequest.connect(self.move_gonio_to_position) - tbox.setLayout(QVBoxLayout()) + def create_helical_widgets(self): + tbox = self._helical_tablebox + htab = self._helical_scan_table = HelicalTableWidget() + htab.gonioMoveRequest.connect(self.move_gonio_to_position) + tbox.setLayout(QVBoxLayout()) - grp = QWidget() - grp.setLayout(QFormLayout()) - le = QSpinBox() - le.setRange(1, 100) - le.setValue(htab.scanHorizontalCount()) - le.valueChanged.connect(lambda cnt: htab.setScanHorizontalCount(cnt)) - grp.layout().addRow("Horizontal Count", le) + grp = QWidget() + grp.setLayout(QFormLayout()) + le = QSpinBox() + le.setRange(1, 100) + le.setValue(htab.scanHorizontalCount()) + le.valueChanged.connect(lambda cnt: htab.setScanHorizontalCount(cnt)) + grp.layout().addRow("Horizontal Count", le) - le = QSpinBox() - le.setRange(1, 100) - le.setValue(htab.scanVerticalCount()) - le.valueChanged.connect(lambda cnt: htab.setScanVerticalCount(cnt)) - grp.layout().addRow("Vertical Count", le) + le = QSpinBox() + le.setRange(1, 100) + le.setValue(htab.scanVerticalCount()) + le.valueChanged.connect(lambda cnt: htab.setScanVerticalCount(cnt)) + grp.layout().addRow("Vertical Count", le) - le = QDoubleSpinBox() - le.setRange(-180.0, 180.0) - le.setSingleStep(5.0) - le.setSuffix(" degrees") - le.valueChanged.connect(htab.setStartAngle) - grp.layout().addRow("Start angle", le) + le = QDoubleSpinBox() + le.setRange(-180.0, 180.0) + le.setSingleStep(5.0) + le.setSuffix(" degrees") + le.valueChanged.connect(htab.setStartAngle) + grp.layout().addRow("Start angle", le) - tbox.layout().addWidget(grp) - widgi = QWidget() - widgi.setLayout(QHBoxLayout()) - tbox.layout().addWidget(widgi) + tbox.layout().addWidget(grp) + widgi = QWidget() + widgi.setLayout(QHBoxLayout()) + tbox.layout().addWidget(widgi) - but = QPushButton("Add Crystal") - but.clicked.connect(htab.add_xtal) - widgi.layout().addWidget(but) + but = QPushButton("Add Crystal") + but.clicked.connect(htab.add_xtal) + widgi.layout().addWidget(but) - but = QPushButton("Set START") - but.clicked.connect(lambda: htab.set_xtal_start(self.get_gonio_positions())) - widgi.layout().addWidget(but) + but = QPushButton("Set START") + but.clicked.connect(lambda: htab.set_xtal_start(self.get_gonio_positions())) + widgi.layout().addWidget(but) - but = QPushButton("Set END") - but.clicked.connect(lambda: htab.set_xtal_end(self.get_gonio_positions())) - widgi.layout().addWidget(but) + but = QPushButton("Set END") + but.clicked.connect(lambda: htab.set_xtal_end(self.get_gonio_positions())) + widgi.layout().addWidget(but) - tbox.layout().addWidget(htab) + tbox.layout().addWidget(htab) - def add_beam_marker(self): - w, h = settings.value(BEAM_SIZE) - self._beammark = BeamMark([100, 100], (int(w), int(h)), parent=self) - self.vb.addItem(self._beammark) + def add_beam_marker(self): + w, h = settings.value(BEAM_SIZE) + self._beammark = BeamMark([100, 100], (int(w), int(h)), parent=self) + self.vb.addItem(self._beammark) - def camera_pause_toggle(self): - sample_camera.pause() + def camera_pause_toggle(self): + app=QApplication.instance() + app._camera.pause() - def updateImage(self, pause=False): - #if not sample_camera.is_paused(): #ZAC: orig. code - img = sample_camera.get_image() - if img is not None: - self.img.setImage(img) + def updateImage(self, pause=False): + #if not sample_camera.is_paused(): #ZAC: orig. code + app=QApplication.instance() + img = app._camera.get_image() + if img is not None: + self.img.setImage(img) - def init_settings_tracker(self): - _log.info("configuring widget persistence") - fields = { - # 'folder': (self._label_folder, str), - "project": (self._le_project, str), - "prefix": (self._le_prefix, str), - "actual_prefix": (self._label_actual_prefix, str), - "exposureTime": (self._dsb_exposure_time, float), - "oscillationAngle": (self._dsb_oscillation_step, float), - "blastRadius": (self._dsb_blast_radius, float), - } - for key, f_config in fields.items(): - widget, conv = f_config - value = settings.value(key) - try: - wset, wget = widget.setText, widget.text - _log.debug("tracking text field {}".format(key)) - except AttributeError: - _log.debug("tracking {} number field {}".format(conv, key)) - wset, wget = widget.setValue, lambda fget=widget.value: conv(fget()) - except Exception as e: - _log.error(e) + def init_settings_tracker(self): + _log.info("configuring widget persistence") + fields = { + # 'folder': (self._label_folder, str), + "project": (self._le_project, str), + "prefix": (self._le_prefix, str), + "actual_prefix": (self._label_actual_prefix, str), + "exposureTime": (self._dsb_exposure_time, float), + "oscillationAngle": (self._dsb_oscillation_step, float), + "blastRadius": (self._dsb_blast_radius, float), + } + for key, f_config in fields.items(): + widget, conv = f_config + value = settings.value(key) + try: + wset, wget = widget.setText, widget.text + _log.debug("tracking text field {}".format(key)) + except AttributeError: + _log.debug("tracking {} number field {}".format(conv, key)) + wset, wget = widget.setValue, lambda fget=widget.value: conv(fget()) + except Exception as e: + _log.error(e) - try: - wset(conv(value)) - except Exception as e: - _log.debug(e) - _log.warning('failed for "{}" updating field of type {} with {}'.format( key, type(widget), value)) - finally: - # _log.debug('wget = {}; wset = {}'.format(wget, wset)) - widget.editingFinished.connect(lambda w=widget, k=key, func_get=wget: self.persist_setting(k, func_get() ) ) - #self.storage_cascade_prefix(None) #ZAC: orig. code + try: + wset(conv(value)) + except Exception as e: + _log.debug(e) + _log.warning('failed for "{}" updating field of type {} with {}'.format( key, type(widget), value)) + finally: + # _log.debug('wget = {}; wset = {}'.format(wget, wset)) + widget.editingFinished.connect(lambda w=widget, k=key, func_get=wget: self.persist_setting(k, func_get() ) ) + #self.storage_cascade_prefix(None) #ZAC: orig. code - def init_validators(self): - identifier_regex = QRegExp("[a-z-A-Z_0-9%]+") - self._le_project.setValidator(QRegExpValidator(identifier_regex, self._le_project)) - self._le_prefix.setValidator(QRegExpValidator(identifier_regex, self._le_prefix)) + def init_validators(self): + identifier_regex = QRegExp("[a-z-A-Z_0-9%]+") + self._le_project.setValidator(QRegExpValidator(identifier_regex, self._le_project)) + self._le_prefix.setValidator(QRegExpValidator(identifier_regex, self._le_prefix)) - def wire_storage(self): - self._le_prefix.textChanged.connect(self.storage_cascade_prefix) - self._le_prefix.textChanged.connect(lambda newtext: self.prefixChanged.emit(newtext)) - self._le_project.textChanged.connect(self.storage_cascade_prefix) - self._le_project.textChanged.connect(lambda newtext: self.projectChanged.emit(newtext)) - self._le_prefix.editingFinished.connect(self.prepare_daq_folder) - self._le_project.editingFinished.connect(self.prepare_daq_folder) - self.increaseRunNumberRequest.connect(self.increase_run_number) + def wire_storage(self): + self._le_prefix.textChanged.connect(self.storage_cascade_prefix) + self._le_prefix.textChanged.connect(lambda newtext: self.prefixChanged.emit(newtext)) + self._le_project.textChanged.connect(self.storage_cascade_prefix) + self._le_project.textChanged.connect(lambda newtext: self.projectChanged.emit(newtext)) + self._le_prefix.editingFinished.connect(self.prepare_daq_folder) + self._le_project.editingFinished.connect(self.prepare_daq_folder) + self.increaseRunNumberRequest.connect(self.increase_run_number) - def storage_cascade_prefix(self, val): - prefix = self._le_prefix.text() - if 0 == len(prefix): - _log.warning("empty prefix is not accepted") - self._le_prefix.setAccessibleName("invalid_input") - self._le_prefix.blockSignals(True) - self._le_prefix.setText("INVALID=>" + prefix) - QMessageBox.warning(self, "prefix is not valid", "Prefix is not valid!") - self._le_prefix.blockSignals(False) + def storage_cascade_prefix(self, val): + prefix = self._le_prefix.text() + if 0 == len(prefix): + _log.warning("empty prefix is not accepted") + self._le_prefix.setAccessibleName("invalid_input") + self._le_prefix.blockSignals(True) + self._le_prefix.setText("INVALID=>" + prefix) + QMessageBox.warning(self, "prefix is not valid", "Prefix is not valid!") + self._le_prefix.blockSignals(False) - return - else: - self._le_prefix.setAccessibleName("") - project = self._le_project.text() - folders.set_prefix(prefix) - folders.set_project(project) - folders.run = settings.value("run_number", type=int) - self._label_runnumber.setText(f"{folders.run:04d}") - self._data_folder = folders.raw_folder - self.folderChanged.emit(folders.raw_folder) - self._label_actual_prefix.setText(folders.prefix) - self._label_folder.setText(folders.raw_folder) + return + else: + self._le_prefix.setAccessibleName("") + project = self._le_project.text() + folders.set_prefix(prefix) + folders.set_project(project) + folders.run = settings.value("run_number", type=int) + self._label_runnumber.setText(f"{folders.run:04d}") + self._data_folder = folders.raw_folder + self.folderChanged.emit(folders.raw_folder) + self._label_actual_prefix.setText(folders.prefix) + self._label_folder.setText(folders.raw_folder) - def increase_run_number(self): - run = settings.value("run_number", type=int) - run += 1 - settings.setValue("run_number", run) - folders.run = run - self._label_runnumber.setText(f"{run:04d}") + def increase_run_number(self): + run = settings.value("run_number", type=int) + run += 1 + settings.setValue("run_number", run) + folders.run = run + self._label_runnumber.setText(f"{run:04d}") - def prepare_daq_folder(self): - global home, folders - prefix = folders.prefix - folder = folders.res_folder - if 0 == len(prefix): - return + def prepare_daq_folder(self): + global home, folders + prefix = folders.prefix + folder = folders.res_folder + if 0 == len(prefix): + return - try: - os.makedirs(folder, 0o750, exist_ok=True) - except: - msg = "Failed to create folder: {}".format(folder) - _log.warning(msg) - QMessageBox.warning(self, "Screenshot: failed to create folder", "Failed to create output folder for screenshot!\n\n\tScreenshot not taken!",) - raise + try: + os.makedirs(folder, 0o750, exist_ok=True) + except: + msg = "Failed to create folder: {}".format(folder) + _log.warning(msg) + QMessageBox.warning(self, "Screenshot: failed to create folder", "Failed to create output folder for screenshot!\n\n\tScreenshot not taken!",) + raise - fname = os.path.join(folders.pgroup_folder, ".latest_raw") - try: - with open(fname, "w") as f: - f.write(folders.raw_folder) - _log.info("wrote: {}".format(fname)) - except: - _log.warning("failed writing {}".format(fname)) + fname = os.path.join(folders.pgroup_folder, ".latest_raw") + try: + with open(fname, "w") as f: + f.write(folders.raw_folder) + _log.info("wrote: {}".format(fname)) + except: + _log.warning("failed writing {}".format(fname)) - fname = os.path.join(folders.pgroup_folder, ".latest_res") - try: - with open(fname, "w") as f: - f.write(folders.res_folder) - _log.info("wrote: {}".format(fname)) - except: - _log.warning("failed writing {}".format(fname)) + fname = os.path.join(folders.pgroup_folder, ".latest_res") + try: + with open(fname, "w") as f: + f.write(folders.res_folder) + _log.info("wrote: {}".format(fname)) + except: + _log.warning("failed writing {}".format(fname)) - def persist_setting(self, s, v): - _log.debug("persisting {} = {}".format(s, v)) - settings.setValue(s, v) + def persist_setting(self, s, v): + _log.debug("persisting {} = {}".format(s, v)) + settings.setValue(s, v) - def method_changed(self, index): - method = self._tabs_daq_methods.currentWidget().accessibleName() - _log.info("method now => {}".format(method)) + def method_changed(self, index): + method = self._tabs_daq_methods.currentWidget().accessibleName() + _log.info("method now => {}".format(method)) - def center_piece_update(self, index): - 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) - else: - #zescape.stop_listening() #ZAC: orig. code - _log.warning("re-starting timer on camera update") - try: - self.timer.stop() - except: - pass - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(self.updateImage) - self.timer.start(10) - - def check_zescape(self): - msg = zescape.check() - if msg is None: - return - if "current" in msg: - _log.warning(f"current state: {self._escape_current_state}") - zescape.reply(self._escape_current_state) - elif "goto" in msg: - state = msg.split()[1].lower() - _log.warning(f"TELL requests to go to {state}") - try: - if "sampleexchange" in state: - _log.debug( - f"moving to mount with offset = {self._pin_mounting_offset}" - ) - self.move_gonio_to_mount_position(offset=self._pin_mounting_offset) - elif "samplealignment" in state: - self.escape_goToSampleAlignment() - except: - zescape.reply("Maintenance") - zescape.reply(self._escape_current_state) - else: # JSON - data = json.loads(msg) - if "sampleName" in data: - _log.debug(f"TELL SAMPLE DATA => {data}") - self.tell2storage(data) - zescape.reply("ack") - elif "pin_offset" in data: - _log.debug(f"TELL pin offset => {data}") - self._pin_mounting_offset = data["pin_offset"] - zescape.reply("ack") - elif "get_pin_offset" in data: - _log.debug(f"TELL get pin offset => {data}") - zescape.reply_json({"pin_offset": self._pin_mounting_offset}) - - def tell2storage(self, sample): - _log.debug(f"2 TELL SAMPLE DATA => {type(sample)}") - self._le_prefix.setText(sample["sampleName"]) - self._le_project.setText(sample["sampleFolder"]) - tstf = folders.get_prefixed_file("_newsample") - self.storage_cascade_prefix(None) - _log.warning(f"sample info: {tstf}") - - def switch_task(self, index=0): - stack = self._centerpiece_stack - task = self._left_tabs.currentWidget().accessibleName() - setup_task = self._setup_toolbox.currentWidget().accessibleName() - method = self._tabs_daq_methods.currentWidget().accessibleName() - - _log.debug("Task switching: top_tabs={} exp.method={} setup_task={}".format(task, method, setup_task)) - - if task == "task_experiment": - stack.setCurrentIndex(0) - active_task = method - elif task == "task_setup": - active_task = setup_task - - elif task == "task_sample_selection": - active_task = TASK_SAMPLE_SELECTION - stack.setCurrentIndex(1) - else: - _log.error("BUG: un-handled task") - QMessageBox.warning(self, "BUG unhandled task!", "unhandled task: top_tabs={} method={}".format(task, method),) - self.set_active_task(active_task) - self._status_task.setText(active_task) - - def set_active_task(self, task): - _log.info("TASK == {}".format(task)) - self._active_task = task - - def active_task(self): - return self._active_task - - def is_task(self, task): - return task == self._active_task - - def get_task_menu(self): + def center_piece_update(self, index): + 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) + else: + #zescape.stop_listening() #ZAC: orig. code + _log.warning("re-starting timer on camera update") + try: + self.timer.stop() + except: pass + self.timer = QtCore.QTimer(self) + self.timer.timeout.connect(self.updateImage) + self.timer.start(10) - def toggle_shutter(self, **kwargs): - if self._pv_shutter: - if 0 == self._pv_shutter.get(): - self._pv_shutter.put(1) - self._button_shutter.setText("shutter opened\n\u2622") - else: - self._pv_shutter.put(0) - self._button_shutter.setText("shutter closed\n\u2622") - elif self._has_pulse_picker: - pulsePicker.toggle() + def check_zescape(self): + msg = zescape.check() + if msg is None: + return + if "current" in msg: + _log.warning(f"current state: {self._escape_current_state}") + zescape.reply(self._escape_current_state) + elif "goto" in msg: + state = msg.split()[1].lower() + _log.warning(f"TELL requests to go to {state}") + try: + if "sampleexchange" in state: + _log.debug( + f"moving to mount with offset = {self._pin_mounting_offset}" + ) + self.move_gonio_to_mount_position(offset=self._pin_mounting_offset) + elif "samplealignment" in state: + self.escape_goToSampleAlignment() + except: + zescape.reply("Maintenance") + zescape.reply(self._escape_current_state) + else: # JSON + data = json.loads(msg) + if "sampleName" in data: + _log.debug(f"TELL SAMPLE DATA => {data}") + self.tell2storage(data) + zescape.reply("ack") + elif "pin_offset" in data: + _log.debug(f"TELL pin offset => {data}") + self._pin_mounting_offset = data["pin_offset"] + zescape.reply("ack") + elif "get_pin_offset" in data: + _log.debug(f"TELL get pin offset => {data}") + zescape.reply_json({"pin_offset": self._pin_mounting_offset}) - def update_shutter_label(self, pvname, value, char_value, **kwargs): - if 0 == value: - self._button_shutter.setText("shutter closed") + def tell2storage(self, sample): + _log.debug(f"2 TELL SAMPLE DATA => {type(sample)}") + self._le_prefix.setText(sample["sampleName"]) + self._le_project.setText(sample["sampleFolder"]) + tstf = folders.get_prefixed_file("_newsample") + self.storage_cascade_prefix(None) + _log.warning(f"sample info: {tstf}") + + def switch_task(self, index=0): + stack = self._centerpiece_stack + task = self._left_tabs.currentWidget().accessibleName() + setup_task = self._setup_toolbox.currentWidget().accessibleName() + method = self._tabs_daq_methods.currentWidget().accessibleName() + + _log.debug("Task switching: top_tabs={} exp.method={} setup_task={}".format(task, method, setup_task)) + + if task == "task_experiment": + stack.setCurrentIndex(0) + active_task = method + elif task == "task_setup": + active_task = setup_task + + elif task == "task_sample_selection": + active_task = TASK_SAMPLE_SELECTION + stack.setCurrentIndex(1) + else: + _log.error("BUG: un-handled task") + QMessageBox.warning(self, "BUG unhandled task!", "unhandled task: top_tabs={} method={}".format(task, method),) + self.set_active_task(active_task) + self._status_task.setText(active_task) + + def set_active_task(self, task): + _log.info("TASK == {}".format(task)) + self._active_task = task + + def active_task(self): + return self._active_task + + def is_task(self, task): + return task == self._active_task + + def get_task_menu(self): + pass + + def toggle_shutter(self, **kwargs): + if self._pv_shutter: + if 0 == self._pv_shutter.get(): + self._pv_shutter.put(1) + self._button_shutter.setText("shutter opened\n\u2622") + else: + self._pv_shutter.put(0) + self._button_shutter.setText("shutter closed\n\u2622") + elif self._has_pulse_picker: + pulsePicker.toggle() + + def update_shutter_label(self, pvname, value, char_value, **kwargs): + if 0 == value: + self._button_shutter.setText("shutter closed") + else: + self._button_shutter.setText("shutter opened") + + def prepare_left_tabs(self): + tabs = self._left_tabs + tabs.currentChanged.connect(self.switch_task) + + setup_tab = self._tab_setup + exp_tab = self._tab_experiment + + # add layouts + exp_tab.setLayout(QVBoxLayout()) + setup_tab.setLayout(QVBoxLayout()) + setup_tab.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) + self._setup_toolbox = tbox = QToolBox() + tbox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)) + # tbox.setMinimumWidth(itemWidget.sizeHint().width()) + + setup_tab.layout().addWidget(tbox) + + # Jungfrau parameters + block = QWidget() + block.setAccessibleName(TASK_JUNGFRAU_SETTINGS) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + #self.jungfrau = jungfrau_widget.JungfrauWidget() #ZAC: orig. code + #block.layout().addWidget(self.jungfrau) #ZAC: orig. code + block.layout().addStretch() + tbox.addItem(block, "Jungfrau Parameters") + + # group beam box + block = QWidget() + block.setAccessibleName(TASK_SETUP_BEAM_CENTER) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + grp = QWidget() + block.layout().addWidget(grp) + block.layout().addStretch() + grp.setLayout(QGridLayout()) + tbox.addItem(block, "Beam Box") + # setup_tab.layout().addWidget(grp) + + row = grp.layout().rowCount + + if self._pv_shutter is not None: + but = QPushButton("open/close shutter") + but.setCheckable(True) + but.setChecked(1 == self._pv_shutter.get()) + but.clicked.connect(self.toggle_shutter) + self._button_shutter = but + grp.layout().addWidget(but, 0, 0, 1, 2) + self._pv_shutter.add_callback(self.update_shutter_label) + #else: #ZAC: orig. code + # self._picker = bernina_pulse_picker.PickerWidget() + # grp.layout().addWidget(self._picker, 0, 0, 1, 2) + but = QPushButton("clear beam markers") + but.clicked.connect(self.remove_beam_markers) + grp.layout().addWidget(but, 1, 1) + help = QTextBrowser() + grp.layout().addWidget(help, 2, 0, 5, 2) + help.setHtml( + """ +

Updating Beam Mark

+
    +
  1. clear beam markers
  2. +
  3. for each zoom level, CTRL-Click (left mouse button) the center of the beam
  4. +
+ + """ + ) + + # group regions + block = QWidget() + block.setAccessibleName(TASK_SETUP_ROI) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + grp = QWidget() + block.layout().addWidget(grp) + block.layout().addStretch() + grp.setLayout(QGridLayout()) + tbox.addItem(block, "Regions of Interest") + but = QPushButton("add RECT roi") + but.clicked.connect(self.roi_add_rect) + grp.layout().addWidget(but, 0, 0) + but = QPushButton("add LINE roi") + but.clicked.connect(self.roi_add_line) + grp.layout().addWidget(but, 0, 1) + + # group regions + block = QWidget() + block.setAccessibleName(TASK_SETUP_CAMERA) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + grp = QWidget() + block.layout().addWidget(grp) + block.layout().addStretch() + grp.setLayout(QGridLayout()) + tbox.addItem(block, "Camera Settings") + but = QPushButton("remove transformations") + but.clicked.connect(lambda bogus: self.modify_camera_transform("remove_all")) + grp.layout().addWidget(but, 0, 0) + but = QPushButton("remove last") + but.clicked.connect(lambda bogus: self.modify_camera_transform("undo_last")) + grp.layout().addWidget(but, 0, 1) + but = QPushButton("Turn 90 CCW") + #but.clicked.connect( + # lambda bogus, n=camera.Transforms.ROTATE_90: self.modify_camera_transform(n) + #) #ZAC: orig. code + grp.layout().addWidget(but, 1, 0) + but = QPushButton("Turn 90 CW") + #but.clicked.connect(lambda bogus, n=camera.Transforms.ROTATE_270: self.modify_camera_transform(n)) #ZAC: orig. code + grp.layout().addWidget(but) + but = QPushButton("Turn 180 CCW") + #but.clicked.connect(lambda bogus, n=camera.Transforms.ROTATE_180: self.modify_camera_transform(n)) #ZAC: orig. code + grp.layout().addWidget(but) + but = QPushButton("Transpose") + #but.clicked.connect(lambda bogus, n=camera.Transforms.TRANSPOSE: self.modify_camera_transform(n)) #ZAC: orig. code + grp.layout().addWidget(but) + but = QPushButton("Flip L/R") + #but.clicked.connect(lambda bogus, n=camera.Transforms.FLIP_LR: self.modify_camera_transform(n)) #ZAC: orig. code + grp.layout().addWidget(but) + but = QPushButton("Flip U/D") + #but.clicked.connect(lambda bogus, n=camera.Transforms.FLIP_UD: self.modify_camera_transform(n)) #ZAC: orig. code + grp.layout().addWidget(but) + row = grp.layout().rowCount() + self._label_transforms = QLabel() + self._label_transforms.setWordWrap(True) + self.modify_camera_transform(None) + + grp.layout().addWidget(self._label_transforms, row, 0, 1, 2) + + # group regions + block = QWidget() + block.setAccessibleName(TASK_SETUP_PPM_CALIBRATION) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + grp = QWidget() + block.layout().addWidget(grp) + block.layout().addStretch() + grp.setLayout(QGridLayout()) + tbox.addItem(block, "Pixel/MM Settings") + grp.layout().addWidget(QLabel("feature size (µM)"), 0, 0) + self._ppm_feature_size_spinbox = QDoubleSpinBox() + self._ppm_feature_size_spinbox.setRange(5, 10000) + self._ppm_feature_size_spinbox.setValue(50) + self._ppm_feature_size_spinbox.setDecimals(0) + self._ppm_feature_size_spinbox.setSuffix(" µM") + grp.layout().addWidget(self._ppm_feature_size_spinbox, 0, 1) + self._ppm_calibration = but = QPushButton("Start calibration") + but.setCheckable(True) + but.clicked.connect(lambda x: self.update_ppm_fitters()) + grp.layout().addWidget(but, 1, 0, 1, 2) + help = QTextBrowser() + grp.layout().addWidget(help, 2, 0, 5, 2) + help.setHtml( + """ +

Pixel/MM Setup

+
    +
  1. choose: feature size
  2. +
  3. press: Start calibration
  4. +
  5. for each zoom level, CTRL-Click (left mouse button) 2 locations which are feature-size apart
  6. +
  7. un-click: Start calibration
  8. +
+ + """ + ) + + # grp = PpmToolBox(viewbox=self.vb, camera=sample_camera, signals=self) + # grp.setAccessibleName(TASK_SETUP_PPM_CALIBRATION_TBOX) + # grp.pixelPerMillimeterCalibrationChanged.connect(self.update_ppm_fitters) + # tbox.addItem(grp, "PPM ToolBox") + # self._ppm_toolbox = grp + + tbox.currentChanged.connect(self.switch_task) + + # final stretch + # setup_tab.layout().addStretch() + exp_tab.layout().addStretch() + + # DAQ Methods Tabs + #self.build_daq_methods_prelocated_tab() #ZAC: orig. code + self.build_daq_methods_grid_tab() + self.build_sample_selection_tab() + + def build_sample_selection_tab(self): + # self._sample_selection = sample_selection.SampleSelection(self) + # self._sample_selection.move_to_mount_position = ( + # self.move_gonio_to_mount_position + # ) + # self._tab_sample_selection.setLayout(QVBoxLayout()) + # self._tab_sample_selection.layout().addWidget(self._sample_selection) + # self._tab_sample_selection.layout().addStretch(2) + pass + + def build_embl_group(self): + grp = QGroupBox("EMBL Acquisition") + layout = QFormLayout() + grp.setLayout(layout) + layout.addWidget(QLabel("Prefix")) + self._embl_prefix = QLineEdit("img") + layout.addWidget(self._embl_prefix) + + def abort_measurement(self, ev=None): + if settings.value(ACTIVATE_PULSE_PICKER): + pulsePicker.close() + jungfrau_detector.abort() + delta_tau.abort() + _log.debug("aborting measurement") + + def trigger_detector(self, **kwargs): + if self._pv_shutter is not None: + self._pv_shutter.put(0) + # self._eiger_button_collect.show() + # self._eiger_button_abort.hide() + # self._eiger_now_collecting_label.setText( + # "Finished sequence id: {}\n" + # "Data in: Data10/{}".format( + # self._detector_sequence_id, self._eiger_now_collecting_file + # ) + # ) + + def modify_camera_transform(self, t): + if t == "remove_all": + sample_camera.set_transformations([]) + elif t == "undo_last": + sample_camera._transformations.pop() + #elif type(t) ==type(camera.Transforms): #ZAC: orig. code + # sample_camera.append_transform(t) + try: + label = ", ".join([t.name for t in sample_camera._transformations]) + except: + label = "" + self._label_transforms.setText(label) + #settings.setValue(CAMERA_TRANSFORMATIONS, sample_camera._transformations) #ZAC: orig. code + + def roi_add_line(self): + roi = pg.LineSegmentROI( + [200, 200], + [300, 300], + pen="r", + scaleSnap=True, + translateSnap=True, + rotateSnap=True, + removable=True, + ) + # roi.sigRegionChanged.connect(self.track_roi) + roi.sigRemoveRequested.connect(self.remove_roi) + self.vb.addItem(roi) + self._rois.append(roi) + + def roi_add_rect(self): + roi = pg.RectROI( + [200, 200], + [50, 50], + pen="r", + scaleSnap=True, + translateSnap=True, + rotateSnap=True, + removable=True, + ) + roi.sigRegionChanged.connect(self.track_roi) + roi.sigRemoveRequested.connect(self.remove_roi) + self.vb.addItem(roi) + self._rois.append(roi) + + def remove_roi(self, roi): + self.vb.removeItem(roi) + self._rois.remove(roi) + + def prepare_embl_gui(self): + self._tab_daq_method_embl.setLayout(QVBoxLayout()) + layout = self._tab_daq_method_embl.layout() + motors = self.get_gonio_motors() + #self._embl_module = EmblWidget(self) #ZAC: orig. code + #self._embl_module.configure(motors, sample_camera, qoptic_zoom) + #layout.addWidget(self._embl_module) + + def prepare_microscope_page(self): + layout = self.microscope_page.layout() + container = QWidget() + hlay = QHBoxLayout() + container.setLayout(hlay) + layout.addWidget(container) + + def update_beam_marker(self, zoom_lvl): + w, h = settings.value(BEAM_SIZE) + try: + bx = self.beamx_fitter(zoom_lvl) + by = self.beamy_fitter(zoom_lvl) + ok = True + except: + ok = False + _log.warning("beam marker not defined") + return + _log.debug("updating beam mark to {:.1f}x{:.1f}".format(bx, by)) + self.beamCameraCoordinatesChanged.emit(bx, by) + + def update_beam_marker_fitters(self): + if len(self._beam_markers) > 2: + _log.debug("defining beam marker") + bx = [(n, x[0]) for n, x in self._beam_markers.items()] + by = [(n, x[1]) for n, x in self._beam_markers.items()] + nbx = np.asarray(bx).T + nby = np.asarray(by).T + bx_coefs = np.polyfit(nbx[0], nbx[1], 3) + by_coefs = np.polyfit(nby[0], nby[1], 3) + _log.debug(".... beam marker X coeficients {}".format(bx_coefs)) + _log.debug(".... beam marker Y coeficients {}".format(by_coefs)) + self.beamx_fitter = np.poly1d(bx_coefs) + self.beamy_fitter = np.poly1d(by_coefs) + + def update_ppm_fitters(self, calib=None): + if calib is not None: + _log.info("received new calibration for PPM") + self._zoom_to_ppm = calib + + # self._zoom_to_ppm = { # FIXME: eventually automate + # 1: 830, + # 100: 940, + # 200: 1220, + # 400: 1950, + # 600: 3460, + # 800: 5400, + # 900: 7150, + # 1000: 9200, + # } + + num_points = len(self._zoom_to_ppm) + if num_points < 2: + return + elif num_points < 4: + order = 2 + elif num_points < 6: + order = 3 + else: + order = 4 + + _log.debug("polynomial fitting using {} data points of order {}".format(num_points, order)) + bx = [(z, ppm) for z, ppm in self._zoom_to_ppm.items()] + nbx = np.asarray(bx).T + bx_coefs = np.polyfit(nbx[0], nbx[1], order) + _log.debug(".... ppm fitting coeficients {}".format(bx_coefs)) + self.ppm_fitter = np.poly1d(bx_coefs) + + def getFastX(self): + return self.tweakers["fast_x"].motor.get_position() + + def getFastY(self): + return self.tweakers["fast_y"].motor.get_position() + + def zoom_changed_cb(self, value): + self.zoomChanged.emit(value) + try: + ppm = self.ppm_fitter(value) + except AttributeError as e: + _log.warning(e) + else: + _log.debug("zoom callback zoomChanged.emit({}), pixelsPerMillimeter.emit({})".format(value, ppm)) + self.pixelsPerMillimeter.emit(ppm) + self.update_beam_marker(value) + + def getZoom(self): + return qoptic_zoom.get_position() + + def getPpm(self): + ppm_fitter = None + try: + ppm_fitter = self.ppm_fitter(self.getZoom()) + except Exception as e: + _log.error("garbage in getPpm()") + traceback.print_exc() + return ppm_fitter + + def append_to_beam_markers(self, x, y, zoom): + self._beam_markers[zoom] = (x, y) + _log.info("beam markers {}".format(self._beam_markers)) + settings.setValue(BEAM_MARKER_POSITIONS, self._beam_markers) + self.update_beam_marker_fitters() + + def remove_beam_markers(self): + self._beam_markers = {} + self.beamx_fitter = None + self.beamy_fitter = None + + def track_roi(self, roi): + x, y = roi.pos() + w, h = roi.size() + # area = roi.getArrayRegion(self._im, self.img) + # sum = np.sum(area) + # _log.info('{} => sum {}'.format((x,y), sum)) + bx, by = x + w / 2., y + h / 2. + _log.info("beam pos {}".format((bx, by))) + _log.info("marker pos = {} ; size = {}".format((x, y), (w, h))) + + def toggle_mouse_tracking(self): + if self._mouse_tracking: + self.disengage_mouse_tracking() + else: + self.engage_mouse_tracking() + + def engage_mouse_tracking(self): + self.glw.scene().sigMouseMoved.connect(self.mouse_move_event) + self.glw.scene().sigMouseMoved.emit() + self._mouse_tracking = True + + def disengage_mouse_tracking(self): + self.glw.scene().sigMouseMoved.disconnect(self.mouse_move_event) + self._mouse_tracking = False + self._lb_coords.setText("") + + def mouse_move_event(self, pos): + self._mouse_pos = pos + task = self.active_task() + xy = self.img.mapFromScene(pos) + z = qoptic_zoom.get_position() + # 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 + x, y = xy.x(), xy.y() + try: + bx, by = self.get_beam_mark_on_camera_xy() + except: + 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 + + self._lb_coords.setText( + "\u23FA{:>}:{:>6.0f} {:<.0f}" + "\u23FA{:>}:{:>6.0f} {:<.0f}" + "\u23FA{:>}:{:<.0f}" + "\u23FA{:>}:{:>6.1f} {:<.1f} \u00B5" + "\u23FA{:>}:{:>7.3f} {:<.3f} mm".format( + "Beam at", bx, by, + "Pixel coord ", x, y, + "PPM", ppm, + "Distance to beam", 1000 * dx, 1000 * dy, + "Stage", fx, fy, + ) + ) + + def mouse_click_event(self, event): + fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers() + _log.debug("{}".format(event)) + _log.debug(" pos {}".format(event.pos())) + _log.debug("screen pos {}".format(event.screenPos())) + _log.debug("scene pos {}".format(event.scenePos())) + + task = self.active_task() + + if event.button() == Qt.RightButton: + print_transform(self.vb.childTransform()) + event.ignore() + return + pos = event.scenePos() + zoom_level = qoptic_zoom.get_position() + _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 + dblclick = event.double() + shft = Qt.ShiftModifier & event.modifiers() + ctrl = Qt.ControlModifier & event.modifiers() + alt = Qt.AltModifier & event.modifiers() + + xy = self.img.mapFromScene(pos) + x, y = xy.x(), xy.y() + bx, by = self.get_beam_mark_on_camera_xy() + a = np.asarray([x, y]) + b = np.asarray([bx, by]) + pixel_dist = np.linalg.norm(a - b) + _log.debug("distance to beam: {:.1f} pixels".format(pixel_dist)) + + omega = omega_motor.motor.get_position() + _log.debug("omega at {:.03f} degrees".format(omega)) + + fx = fx_motor.motor.get_position() + fy = fy_motor.motor.get_position() + dx = (x - bx) / ppm + dy = -(y - by) / ppm + fx += dx + fy += dy + + _log.debug("click at : {:.3f} {:.3f}".format(x, y)) + _log.debug("beam at : {:.3f} {:.3f}".format(bx, by)) + _log.debug("gonio at : {:.3f} {:.3f}".format(fx, fy)) + + # publisher.submit( + # {"event": "gonio", "data": self.get_gonio_positions(as_json=True)} + # ) + _log.debug("TASK = {}".format(task)) + # Click without modifers moves clicked position to beam mark + if task not in (TASK_SETUP_PPM_CALIBRATION, TASK_SETUP_BEAM_CENTER): + if not (ctrl or shft or alt): + if ppm == 0: + _log.warning("PPM is not defined - do the PPM calibration") + + if task == TASK_HELICAL: + dx = (x - bx) / ppm + dy = -(y - by) / ppm + _log.info("moving base_X, fast_Y {:.3f}, {:.3f}".format(dx, dy)) + bx_motor.move_relative(dx) + fy_motor.move_relative(dy) else: - self._button_shutter.setText("shutter opened") - - def prepare_left_tabs(self): - tabs = self._left_tabs - tabs.currentChanged.connect(self.switch_task) - - setup_tab = self._tab_setup - exp_tab = self._tab_experiment - - # add layouts - exp_tab.setLayout(QVBoxLayout()) - setup_tab.setLayout(QVBoxLayout()) - setup_tab.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) - self._setup_toolbox = tbox = QToolBox() - tbox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)) - # tbox.setMinimumWidth(itemWidget.sizeHint().width()) - - setup_tab.layout().addWidget(tbox) - - # Jungfrau parameters - block = QWidget() - block.setAccessibleName(TASK_JUNGFRAU_SETTINGS) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - #self.jungfrau = jungfrau_widget.JungfrauWidget() #ZAC: orig. code - #block.layout().addWidget(self.jungfrau) #ZAC: orig. code - block.layout().addStretch() - tbox.addItem(block, "Jungfrau Parameters") - - # group beam box - block = QWidget() - block.setAccessibleName(TASK_SETUP_BEAM_CENTER) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - grp = QWidget() - block.layout().addWidget(grp) - block.layout().addStretch() - grp.setLayout(QGridLayout()) - tbox.addItem(block, "Beam Box") - # setup_tab.layout().addWidget(grp) - - row = grp.layout().rowCount - - if self._pv_shutter is not None: - but = QPushButton("open/close shutter") - but.setCheckable(True) - but.setChecked(1 == self._pv_shutter.get()) - but.clicked.connect(self.toggle_shutter) - self._button_shutter = but - grp.layout().addWidget(but, 0, 0, 1, 2) - self._pv_shutter.add_callback(self.update_shutter_label) - #else: #ZAC: orig. code - # self._picker = bernina_pulse_picker.PickerWidget() - # grp.layout().addWidget(self._picker, 0, 0, 1, 2) - but = QPushButton("clear beam markers") - but.clicked.connect(self.remove_beam_markers) - grp.layout().addWidget(but, 1, 1) - help = QTextBrowser() - grp.layout().addWidget(help, 2, 0, 5, 2) - help.setHtml( - """ -

Updating Beam Mark

-
    -
  1. clear beam markers
  2. -
  3. for each zoom level, CTRL-Click (left mouse button) the center of the beam
  4. -
- - """ - ) - - # group regions - block = QWidget() - block.setAccessibleName(TASK_SETUP_ROI) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - grp = QWidget() - block.layout().addWidget(grp) - block.layout().addStretch() - grp.setLayout(QGridLayout()) - tbox.addItem(block, "Regions of Interest") - but = QPushButton("add RECT roi") - but.clicked.connect(self.roi_add_rect) - grp.layout().addWidget(but, 0, 0) - but = QPushButton("add LINE roi") - but.clicked.connect(self.roi_add_line) - grp.layout().addWidget(but, 0, 1) - - # group regions - block = QWidget() - block.setAccessibleName(TASK_SETUP_CAMERA) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - grp = QWidget() - block.layout().addWidget(grp) - block.layout().addStretch() - grp.setLayout(QGridLayout()) - tbox.addItem(block, "Camera Settings") - but = QPushButton("remove transformations") - but.clicked.connect(lambda bogus: self.modify_camera_transform("remove_all")) - grp.layout().addWidget(but, 0, 0) - but = QPushButton("remove last") - but.clicked.connect(lambda bogus: self.modify_camera_transform("undo_last")) - grp.layout().addWidget(but, 0, 1) - but = QPushButton("Turn 90 CCW") - #but.clicked.connect( - # lambda bogus, n=camera.Transforms.ROTATE_90: self.modify_camera_transform(n) - #) #ZAC: orig. code - grp.layout().addWidget(but, 1, 0) - but = QPushButton("Turn 90 CW") - #but.clicked.connect(lambda bogus, n=camera.Transforms.ROTATE_270: self.modify_camera_transform(n)) #ZAC: orig. code - grp.layout().addWidget(but) - but = QPushButton("Turn 180 CCW") - #but.clicked.connect(lambda bogus, n=camera.Transforms.ROTATE_180: self.modify_camera_transform(n)) #ZAC: orig. code - grp.layout().addWidget(but) - but = QPushButton("Transpose") - #but.clicked.connect(lambda bogus, n=camera.Transforms.TRANSPOSE: self.modify_camera_transform(n)) #ZAC: orig. code - grp.layout().addWidget(but) - but = QPushButton("Flip L/R") - #but.clicked.connect(lambda bogus, n=camera.Transforms.FLIP_LR: self.modify_camera_transform(n)) #ZAC: orig. code - grp.layout().addWidget(but) - but = QPushButton("Flip U/D") - #but.clicked.connect(lambda bogus, n=camera.Transforms.FLIP_UD: self.modify_camera_transform(n)) #ZAC: orig. code - grp.layout().addWidget(but) - row = grp.layout().rowCount() - self._label_transforms = QLabel() - self._label_transforms.setWordWrap(True) - self.modify_camera_transform(None) - - grp.layout().addWidget(self._label_transforms, row, 0, 1, 2) - - # group regions - block = QWidget() - block.setAccessibleName(TASK_SETUP_PPM_CALIBRATION) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - grp = QWidget() - block.layout().addWidget(grp) - block.layout().addStretch() - grp.setLayout(QGridLayout()) - tbox.addItem(block, "Pixel/MM Settings") - grp.layout().addWidget(QLabel("feature size (µM)"), 0, 0) - self._ppm_feature_size_spinbox = QDoubleSpinBox() - self._ppm_feature_size_spinbox.setRange(5, 10000) - self._ppm_feature_size_spinbox.setValue(50) - self._ppm_feature_size_spinbox.setDecimals(0) - self._ppm_feature_size_spinbox.setSuffix(" µM") - grp.layout().addWidget(self._ppm_feature_size_spinbox, 0, 1) - self._ppm_calibration = but = QPushButton("Start calibration") - but.setCheckable(True) - but.clicked.connect(lambda x: self.update_ppm_fitters()) - grp.layout().addWidget(but, 1, 0, 1, 2) - help = QTextBrowser() - grp.layout().addWidget(help, 2, 0, 5, 2) - help.setHtml( - """ -

Pixel/MM Setup

-
    -
  1. choose: feature size
  2. -
  3. press: Start calibration
  4. -
  5. for each zoom level, CTRL-Click (left mouse button) 2 locations which are feature-size apart
  6. -
  7. un-click: Start calibration
  8. -
- - """ - ) - - # grp = PpmToolBox(viewbox=self.vb, camera=sample_camera, signals=self) - # grp.setAccessibleName(TASK_SETUP_PPM_CALIBRATION_TBOX) - # grp.pixelPerMillimeterCalibrationChanged.connect(self.update_ppm_fitters) - # tbox.addItem(grp, "PPM ToolBox") - # self._ppm_toolbox = grp - - tbox.currentChanged.connect(self.switch_task) - - # final stretch - # setup_tab.layout().addStretch() - exp_tab.layout().addStretch() - - # DAQ Methods Tabs - #self.build_daq_methods_prelocated_tab() #ZAC: orig. code - self.build_daq_methods_grid_tab() - self.build_sample_selection_tab() - - def build_sample_selection_tab(self): - # self._sample_selection = sample_selection.SampleSelection(self) - # self._sample_selection.move_to_mount_position = ( - # self.move_gonio_to_mount_position - # ) - # self._tab_sample_selection.setLayout(QVBoxLayout()) - # self._tab_sample_selection.layout().addWidget(self._sample_selection) - # self._tab_sample_selection.layout().addStretch(2) - pass - - def build_embl_group(self): - grp = QGroupBox("EMBL Acquisition") - layout = QFormLayout() - grp.setLayout(layout) - layout.addWidget(QLabel("Prefix")) - self._embl_prefix = QLineEdit("img") - layout.addWidget(self._embl_prefix) - - def abort_measurement(self, ev=None): - if settings.value(ACTIVATE_PULSE_PICKER): - pulsePicker.close() - jungfrau_detector.abort() - delta_tau.abort() - _log.debug("aborting measurement") - - def trigger_detector(self, **kwargs): - if self._pv_shutter is not None: - self._pv_shutter.put(0) - # self._eiger_button_collect.show() - # self._eiger_button_abort.hide() - # self._eiger_now_collecting_label.setText( - # "Finished sequence id: {}\n" - # "Data in: Data10/{}".format( - # self._detector_sequence_id, self._eiger_now_collecting_file - # ) - # ) - - def modify_camera_transform(self, t): - if t == "remove_all": - sample_camera.set_transformations([]) - elif t == "undo_last": - sample_camera._transformations.pop() - #elif type(t) ==type(camera.Transforms): #ZAC: orig. code - # sample_camera.append_transform(t) - try: - label = ", ".join([t.name for t in sample_camera._transformations]) - except: - label = "" - self._label_transforms.setText(label) - #settings.setValue(CAMERA_TRANSFORMATIONS, sample_camera._transformations) #ZAC: orig. code - - def roi_add_line(self): - roi = pg.LineSegmentROI( - [200, 200], - [300, 300], - pen="r", - scaleSnap=True, - translateSnap=True, - rotateSnap=True, - removable=True, - ) - # roi.sigRegionChanged.connect(self.track_roi) - roi.sigRemoveRequested.connect(self.remove_roi) - self.vb.addItem(roi) - self._rois.append(roi) - - def roi_add_rect(self): - roi = pg.RectROI( - [200, 200], - [50, 50], - pen="r", - scaleSnap=True, - translateSnap=True, - rotateSnap=True, - removable=True, - ) - roi.sigRegionChanged.connect(self.track_roi) - roi.sigRemoveRequested.connect(self.remove_roi) - self.vb.addItem(roi) - self._rois.append(roi) - - def remove_roi(self, roi): - self.vb.removeItem(roi) - self._rois.remove(roi) - - def prepare_embl_gui(self): - self._tab_daq_method_embl.setLayout(QVBoxLayout()) - layout = self._tab_daq_method_embl.layout() - motors = self.get_gonio_motors() - #self._embl_module = EmblWidget(self) #ZAC: orig. code - #self._embl_module.configure(motors, sample_camera, qoptic_zoom) - #layout.addWidget(self._embl_module) - - def prepare_microscope_page(self): - layout = self.microscope_page.layout() - container = QWidget() - hlay = QHBoxLayout() - container.setLayout(hlay) - layout.addWidget(container) - - def update_beam_marker(self, zoom_lvl): - w, h = settings.value(BEAM_SIZE) - try: - bx = self.beamx_fitter(zoom_lvl) - by = self.beamy_fitter(zoom_lvl) - ok = True - except: - ok = False - _log.warning("beam marker not defined") - return - _log.debug("updating beam mark to {:.1f}x{:.1f}".format(bx, by)) - self.beamCameraCoordinatesChanged.emit(bx, by) - - def update_beam_marker_fitters(self): - if len(self._beam_markers) > 2: - _log.debug("defining beam marker") - bx = [(n, x[0]) for n, x in self._beam_markers.items()] - by = [(n, x[1]) for n, x in self._beam_markers.items()] - nbx = np.asarray(bx).T - nby = np.asarray(by).T - bx_coefs = np.polyfit(nbx[0], nbx[1], 3) - by_coefs = np.polyfit(nby[0], nby[1], 3) - _log.debug(".... beam marker X coeficients {}".format(bx_coefs)) - _log.debug(".... beam marker Y coeficients {}".format(by_coefs)) - self.beamx_fitter = np.poly1d(bx_coefs) - self.beamy_fitter = np.poly1d(by_coefs) - - def update_ppm_fitters(self, calib=None): - if calib is not None: - _log.info("received new calibration for PPM") - self._zoom_to_ppm = calib - - # self._zoom_to_ppm = { # FIXME: eventually automate - # 1: 830, - # 100: 940, - # 200: 1220, - # 400: 1950, - # 600: 3460, - # 800: 5400, - # 900: 7150, - # 1000: 9200, - # } - - num_points = len(self._zoom_to_ppm) - if num_points < 2: - return - elif num_points < 4: - order = 2 - elif num_points < 6: - order = 3 - else: - order = 4 - - _log.debug("polynomial fitting using {} data points of order {}".format(num_points, order)) - bx = [(z, ppm) for z, ppm in self._zoom_to_ppm.items()] - nbx = np.asarray(bx).T - bx_coefs = np.polyfit(nbx[0], nbx[1], order) - _log.debug(".... ppm fitting coeficients {}".format(bx_coefs)) - self.ppm_fitter = np.poly1d(bx_coefs) - - def getFastX(self): - return self.tweakers["fast_x"].motor.get_position() - - def getFastY(self): - return self.tweakers["fast_y"].motor.get_position() - - def zoom_changed_cb(self, value): - self.zoomChanged.emit(value) - try: - ppm = self.ppm_fitter(value) - except AttributeError as e: - _log.warning(e) - else: - _log.debug("zoom callback zoomChanged.emit({}), pixelsPerMillimeter.emit({})".format(value, ppm)) - self.pixelsPerMillimeter.emit(ppm) - self.update_beam_marker(value) - - def getZoom(self): - return qoptic_zoom.get_position() - - def getPpm(self): - ppm_fitter = None - try: - ppm_fitter = self.ppm_fitter(self.getZoom()) - except Exception as e: - _log.error("garbage in getPpm()") - traceback.print_exc() - return ppm_fitter - - def append_to_beam_markers(self, x, y, zoom): - self._beam_markers[zoom] = (x, y) - _log.info("beam markers {}".format(self._beam_markers)) - settings.setValue(BEAM_MARKER_POSITIONS, self._beam_markers) - self.update_beam_marker_fitters() - - def remove_beam_markers(self): - self._beam_markers = {} - self.beamx_fitter = None - self.beamy_fitter = None - - def track_roi(self, roi): - x, y = roi.pos() - w, h = roi.size() - # area = roi.getArrayRegion(self._im, self.img) - # sum = np.sum(area) - # _log.info('{} => sum {}'.format((x,y), sum)) - bx, by = x + w / 2., y + h / 2. - _log.info("beam pos {}".format((bx, by))) - _log.info("marker pos = {} ; size = {}".format((x, y), (w, h))) - - def toggle_mouse_tracking(self): - if self._mouse_tracking: - self.disengage_mouse_tracking() - else: - self.engage_mouse_tracking() - - def engage_mouse_tracking(self): - self.glw.scene().sigMouseMoved.connect(self.mouse_move_event) - self.glw.scene().sigMouseMoved.emit() - self._mouse_tracking = True - - def disengage_mouse_tracking(self): - self.glw.scene().sigMouseMoved.disconnect(self.mouse_move_event) - self._mouse_tracking = False - self._lb_coords.setText("") - - def mouse_move_event(self, pos): - self._mouse_pos = pos - task = self.active_task() - xy = self.img.mapFromScene(pos) - z = qoptic_zoom.get_position() - # 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 - x, y = xy.x(), xy.y() - try: - bx, by = self.get_beam_mark_on_camera_xy() - except: - 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 - - self._lb_coords.setText( - "\u23FA{:>}:{:>6.0f} {:<.0f}" - "\u23FA{:>}:{:>6.0f} {:<.0f}" - "\u23FA{:>}:{:<.0f}" - "\u23FA{:>}:{:>6.1f} {:<.1f} \u00B5" - "\u23FA{:>}:{:>7.3f} {:<.3f} mm".format( - "Beam at", bx, by, - "Pixel coord ", x, y, - "PPM", ppm, - "Distance to beam", 1000 * dx, 1000 * dy, - "Stage", fx, fy, - ) - ) - - def mouse_click_event(self, event): - fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers() - _log.debug("{}".format(event)) - _log.debug(" pos {}".format(event.pos())) - _log.debug("screen pos {}".format(event.screenPos())) - _log.debug("scene pos {}".format(event.scenePos())) - - task = self.active_task() - - if event.button() == Qt.RightButton: - print_transform(self.vb.childTransform()) - event.ignore() - return - pos = event.scenePos() - zoom_level = qoptic_zoom.get_position() - _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 - dblclick = event.double() - shft = Qt.ShiftModifier & event.modifiers() - ctrl = Qt.ControlModifier & event.modifiers() - alt = Qt.AltModifier & event.modifiers() - - xy = self.img.mapFromScene(pos) - x, y = xy.x(), xy.y() - bx, by = self.get_beam_mark_on_camera_xy() - a = np.asarray([x, y]) - b = np.asarray([bx, by]) - pixel_dist = np.linalg.norm(a - b) - _log.debug("distance to beam: {:.1f} pixels".format(pixel_dist)) - - omega = omega_motor.motor.get_position() - _log.debug("omega at {:.03f} degrees".format(omega)) - - fx = fx_motor.motor.get_position() - fy = fy_motor.motor.get_position() - dx = (x - bx) / ppm - dy = -(y - by) / ppm - fx += dx - fy += dy - - _log.debug("click at : {:.3f} {:.3f}".format(x, y)) - _log.debug("beam at : {:.3f} {:.3f}".format(bx, by)) - _log.debug("gonio at : {:.3f} {:.3f}".format(fx, fy)) - - # publisher.submit( - # {"event": "gonio", "data": self.get_gonio_positions(as_json=True)} - # ) - _log.debug("TASK = {}".format(task)) - # Click without modifers moves clicked position to beam mark - if task not in (TASK_SETUP_PPM_CALIBRATION, TASK_SETUP_BEAM_CENTER): - if not (ctrl or shft or alt): - if ppm == 0: - _log.warning("PPM is not defined - do the PPM calibration") - - if task == TASK_HELICAL: - dx = (x - bx) / ppm - dy = -(y - by) / ppm - _log.info("moving base_X, fast_Y {:.3f}, {:.3f}".format(dx, dy)) - bx_motor.move_relative(dx) - fy_motor.move_relative(dy) - else: - dx = np.cos(omega * np.pi / 180) * (x - bx) / ppm - dy = -(y - by) / ppm - _log.info("moving fast x/y to {:.3f}, {:.3f}".format(dx, dy)) - fx_motor.move_relative(dx) - fy_motor.move_relative(dy) - self.fast_dx_position.emit(dx) - self.fast_dy_position.emit(dy) - return - - # CTRL-Click always just moves Z - if ctrl and not (shft or alt): - mm = np.sign(y - by) * pixel_dist / 10000. - _log.debug("moving Z stage by {:.3f} mm".format(mm)) - bz_motor.move_relative(mm) - return - - if task in (TASK_SETUP_PPM_CALIBRATION, TASK_SETUP_BEAM_CENTER): - if ctrl and self._ppm_calibration.isChecked(): - if self._ppm_click is not None: - feature_dist = self._ppm_feature_size_spinbox.value() / 1000. - b = self._ppm_click - pixel_dist = np.linalg.norm(a - b) - ppm = pixel_dist / feature_dist - _log.debug("calib save a {}".format(a)) - _log.debug("calib save b {}".format(b)) - _log.debug(" feature {}".format(feature_dist)) - _log.debug("a -> b pixels {}".format(pixel_dist)) - _log.debug(" ppm({}) {}".format(zoom_level, pixel_dist)) - - self._zoom_to_ppm[zoom_level] = ppm - settings.setValue(CAMERA_ZOOM_TO_PPM, self._zoom_to_ppm) - self._ppm_click = None - _log.debug("ppm data so far {}".format(self._zoom_to_ppm)) - else: - _log.debug("calib save b {}".format(a)) - self._ppm_click = a - return - elif task == TASK_SETUP_BEAM_CENTER: - if ctrl and not (shft or alt): - _log.info("beam mark add: zoom {} => {:.1f} {:.1f}".format(zoom_level, x, y)) - self.append_to_beam_markers(x, y, zoom_level) - else: - _log.warning("click combination not available") - elif task == TASK_POINT_SHOOT: - pass - elif task == TASK_GRID: - if shft and not (ctrl or alt): - self.addGridRequest.emit(fx, fy) - elif task == TASK_PRELOCATED: - if ctrl and shft: - omegacos = option(DELTATAU_OMEGACOS) - if omegacos: - fx = fx / np.cos(omega * np.pi / 180) - _log.info( - "selectin fiducial (omegacos): {:.3f}, {:.3f}".format(fx, fy) - ) - else: - _log.info("selectin fiducial: {:.3f}, {:.3f}".format(fx, fy)) - self.fiducialPositionSelected.emit(x, y, fx, fy) - elif shft and alt: - self.appendPrelocatedPosition.emit(x, y, fx, fy) - elif task == TASK_HELICAL: - _log.info("no action associated ") - elif task == TASK_EMBL: - pass - else: - pass - - def get_beam_mark_on_camera_xy(self): - z = qoptic_zoom.get_position() - try: - bx = self.beamx_fitter(z) - by = self.beamy_fitter(z) - except: - bx, by = 500, 500 - return (bx, by) - - def safe_backlight_move(self, pos): - # any move of backlight requires post sample tube out - try: - self.assert_post_tube_position(pos="out") - except: - self.move_post_tube("out") - backlight.move(pos) - - def escape_run_steps(self, steps, title): - with pg.ProgressDialog(title, 0, len(steps)) as dlg: - for step in steps: - step() - dlg += 1 - if dlg.wasCanceled(): - raise TransitionAborted("ABORTED" + title) - - def escape_goToSampleExchange(self): - self._escape_current_state = "busy" - steps = [] - if option(CRYOJET_MOTION_ENABLED): - steps.append(lambda: self.move_cryojet_nozzle("out")) - steps.extend( - [ - lambda: self.move_post_tube("out"), - lambda: backlight.move("out", wait=True), - lambda: self.move_collimator("out"), - ] - ) - self.escape_run_steps(steps, "Transitioning to Sample Exchange") - self._escape_current_state = "ManualSampleExchange" - - def escape_goToSampleAlignment(self): - self._escape_current_state = "busy" - - steps = [ - # lambda: sample_selection.tell.set_current(30.0), - lambda: self.move_collimator("ready") - ] - 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")]) - self.escape_run_steps(steps, "Transitioning to Sample Alignment") - self._escape_current_state = "SampleAlignment" - - def escape_goToDataCollection(self): - self._escape_current_state = "busy" - steps = [ - # lambda: sample_selection.tell.set_current(30.0), - lambda: backlight.move("out", assert_positions=True), - lambda: self.move_post_tube("in"), - lambda: self.move_collimator("in"), - ] - if option(CRYOJET_MOTION_ENABLED): - steps.extend([lambda: self.move_cryojet_nozzle("in")]) - self.escape_run_steps(steps, "Transitioning to Data Collection") - self._escape_current_state = "DataCollection" - - def move_gonio_to_position(self, fx, fy, bx, bz, omega): - self.tweakers["fast_x"].motor.move(fx, wait=False, ignore_limits=True) - self.tweakers["fast_y"].motor.move(fy, wait=False, ignore_limits=True) - self.tweakers["base_x"].motor.move(bx, wait=False, ignore_limits=True) - self.tweakers["base_z"].motor.move(bz, wait=False, ignore_limits=True) - self.tweakers["omega"].motor.move(omega, wait=False, ignore_limits=True) - - def get_gonio_motors(self, as_json=False): - if as_json: - return { - "fast_x": self.tweakers["fast_x"].motor, - "fast_y": self.tweakers["fast_y"].motor, - "base_x": self.tweakers["base_x"].motor, - "base_z": self.tweakers["base_z"].motor, - "omega": self.tweakers["omega"].motor, - } - else: - return ( - self.tweakers["fast_x"].motor, - self.tweakers["fast_y"].motor, - self.tweakers["base_x"].motor, - self.tweakers["base_z"].motor, - self.tweakers["omega"].motor, - ) - - def get_gonio_tweakers(self): - return ( - self.tweakers["fast_x"], - self.tweakers["fast_y"], - self.tweakers["base_x"], - self.tweakers["base_z"], - self.tweakers["omega"], - ) - - def get_gonio_positions(self, as_json: bool = False): - fx, fy, cx, cz, omega = ( - self.tweakers["fast_x"].motor, - self.tweakers["fast_y"].motor, - self.tweakers["base_x"].motor, - self.tweakers["base_z"].motor, - self.tweakers["omega"].motor, - ) - - a, b, c, d, e = ( - fx.get_position(), - fy.get_position(), - cx.get_position(), - cz.get_position(), - omega.get_position(), - ) - if as_json: - return {"fx": a, "fy": b, "bx": c, "bz": d, "omega": e} - else: - return (a, b, c, d, e) - - def escape_goToTellMountPosition(self): - self.move_gonio_to_mount_position() - self.lock_goniometer() - - def move_gonio_to_mount_position(self, offset: float = 0.0): - fx, fy, cx, cz, omega = self.get_gonio_motors() - bmark = "bookmark_0" - try: - t_fx = float(settings.value(bmark + "/mount_fx")) - t_fy = -offset + float(settings.value(bmark + "/mount_fy")) - t_cx = float(settings.value(bmark + "/mount_cx")) - t_cz = float(settings.value(bmark + "/mount_cz")) - t_omega = float(settings.value(bmark + "/mount_omega")) - except: - raise IncompleteConfiguration("TELL sample changer mount position is not configured!!!") - fx.move(t_fx, wait=True, ignore_limits=True) - fy.move(t_fy, wait=True, ignore_limits=True) - cx.move(t_cx, wait=True, ignore_limits=True) - cz.move(t_cz, wait=True, ignore_limits=True) - omega.move(t_omega, wait=True, ignore_limits=True) - app_utils.assert_motor_positions( - [ - (fx, t_fx, 0.01), - (fy, t_fy, 0.01), - (cx, t_cx, 0.01), - (cz, t_cz, 0.01), - (omega, t_omega, 0.01), - ] - ) - self.escape_goToSampleExchange() - - def lock_goniometer(self): - # tell.set_in_mount_position(True) - res = QMessageBox.question(self, "", "Mount a sample from console and click ok once the sample is mounted.", QMessageBox.Ok, QMessageBox.Ok,) - res = QMessageBox.question(self, "", "Is the sample is mounted?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) - if res in (QMessageBox.Yes, QMessageBox.No): - # tell.set_in_mount_position(False) - pass - - @pyqtSlot(int) - def saveBookmark(self, key: int): - """save a bookmark for the corresponding key is the Qt.Key_* code """ - fx, fy, cx, cz, omega = ( - self.tweakers["fast_x"].motor, - self.tweakers["fast_y"].motor, - self.tweakers["base_x"].motor, - self.tweakers["base_z"].motor, - self.tweakers["omega"].motor, - ) - bmark = "bookmark_{}".format(key) - if key == 0: - ans = QMessageBox.question(self, "Override TELL mount position", "This will overwrite the positions used for TELL MOUNTING!!!\n\n\tContinue ?",) - if ans != QMessageBox.Yes: - return - - _log.info( - "saving bookmark {}: {}, {}, {}, {}, {}".format( - bmark, - fx.get_position(), - fy.get_position(), - cx.get_position(), - cz.get_position(), - omega.get_position(), - ) - ) - settings.setValue(bmark + "/mount_fx", fx.get_position()) - settings.setValue(bmark + "/mount_fy", fy.get_position()) - settings.setValue(bmark + "/mount_cx", cx.get_position()) - settings.setValue(bmark + "/mount_cz", cz.get_position()) - settings.setValue(bmark + "/mount_omega", omega.get_position()) - - def gotoBookmark(self, key: int): - """save a bookmark for the corresponding key""" - fx, fy, cx, cz, omega = ( - self.tweakers["fast_x"].motor, - self.tweakers["fast_y"].motor, - self.tweakers["base_x"].motor, - self.tweakers["base_z"].motor, - self.tweakers["omega"].motor, - ) - bmark = "bookmark_{}".format(key) - try: - t_fx = float(settings.value(bmark + "/mount_fx")) - t_fy = float(settings.value(bmark + "/mount_fy")) - t_cx = float(settings.value(bmark + "/mount_cx")) - t_cz = float(settings.value(bmark + "/mount_cz")) - t_omega = float(settings.value(bmark + "/mount_omega")) - except: - return - - fx.move(t_fx, wait=True, ignore_limits=True) - fy.move(t_fy, wait=True, ignore_limits=True) - cx.move(t_cx, wait=True, ignore_limits=True) - cz.move(t_cz, wait=True, ignore_limits=True) - omega.move(t_omega, wait=True, ignore_limits=True) - - def move_collimator(self, pos): - cx = self.tweakers["colli_x"] - cy = self.tweakers["colli_y"] - - x_pos = settings.value("collimator/x_in", 1e10, type=float) - y_pos = settings.value("collimator/y_in", 1e10, type=float) - dx = settings.value("collimator/dx", 1e10, type=float) - dy = settings.value("collimator/dy", 1e10, type=float) - - if 1e9 < x_pos + y_pos + dx + dy: - raise IncompleteConfiguration("COLLIMATOR configuration is incomplete!") - - _log.info("moving collimator {} to X,Y = {:.3f}, {:.3f}".format(pos, x_pos, y_pos)) - - if pos == "out": - cy.move_motor_to_position(y_pos + dy, assert_position=True) - cx.move_motor_to_position(x_pos + dx, assert_position=True) - elif pos == "in": - cx.move_motor_to_position(x_pos, assert_position=True) - cy.move_motor_to_position(y_pos, assert_position=True) - elif pos == "ready": - cx.move_motor_to_position(x_pos, assert_position=True) - cy.move_motor_to_position(y_pos + dy, assert_position=True) - else: - raise UnknownLabeledPosition("Collimator position *{}* is not known!!") - - def move_cryojet_nozzle(self, pos): - cx = self.tweakers["cryo"] - if "in" == pos.lower(): - key = CRYOJET_NOZZLE_IN - elif "out" == pos.lower(): - key = CRYOJET_NOZZLE_OUT - - to_pos = settings.value(key, 1e10, type=float) - if to_pos > 1e9: - raise IncompleteConfiguration(f"CRYOJET configuration is incomplete! Missing {key}") - cx.move_motor_to_position(to_pos, assert_position=True) - - def setup_sliders(self): - cont = self._rightmost - self._tweak_container = QWidget() - self._tweak_container.setLayout(QVBoxLayout()) - cont.layout().addWidget(self._tweak_container) - layout = self._tweak_container.layout() - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) - if self._at_x06sa: - zoom_base = "ESBMX-SAMCAM" # fetura IOC by Jose Gabadinho - elif self._at_lab_eh060: - zoom_base = "/dev/ttyUSB0" # direct connection using fetura.py package - elif self._at_cristalina: - zoom_base = ("rest://pc12818.psi.ch:9999") # direct connection using fetura.py package - - self.zoombox = zoom.Zoom() - self.zoombox.configure() - self.zoombox.zoomChanged.connect(self.zoom_changed_cb) - self.zoombox.moveBacklight.connect(self.safe_backlight_move) - - layout.addWidget(self.zoombox) - - toolbox = QToolBox() - layout.addWidget(toolbox) - - self.build_faststage_group(toolbox) - # self.build_slits_group(toolbox) - self.build_collimator_group(toolbox) - self.build_posttube_group(toolbox) - self.build_xeye_group(toolbox) - self.build_cryo_group(toolbox) - - # monitor all axis for an axis fault - for key, tweaker in self.tweakers.items(): - tweaker.event_axis_fault.connect(self.axis_fault) - - self.tweakers["fast_x"].event_readback.connect(lambda alias, value, kw: self.fast_x_position.emit(value)) - self.tweakers["fast_y"].event_readback.connect(lambda alias, value, kw: self.fast_y_position.emit(value)) - - # layout.addStretch() - - def axis_fault(self, pvname, kw): - """ - swissmx - {'pvname': 'SAR-EXPMX:MOT_FY.STAT', 'value': 0, 'char_value': 'NO_ALARM', 'status': 0, 'ftype': 17, 'chid': 38980392, - 'host': 'SAR-CPPM-EXPMX1.psi.ch:5064', 'count': 1, 'access': 'read-only', 'write_access': False, 'read_access': True, - 'severity': 0, - 'timestamp': 1537867290.914189, 'precision': None, 'units': None, 'enum_strs': ('NO_ALARM', 'READ', 'WRITE', 'HIHI', 'HIGH', 'LOLO', 'LOW', 'STATE', 'COS', 'COMM', 'TIMEOUT', 'HWLIMIT', 'CALC', 'SCAN', 'LINK', 'SOFT'), 'upper_disp_limit': None, 'lower_disp_limit': None, 'upper_alarm_limit': None, 'lower_alarm_limit': None, 'lower_warning_limit': None, 'upper_warning_limit': None, 'upper_ctrl_limit': None, 'lower_ctrl_limit': None, 'nelm': 1, 'type': 'time_enum', 'typefull': 'time_enum', 'motor_field': 'STAT', 'source_field': 'STAT', 'cb_info': (1, ), 'alias': 'fast Y'} - - :param pvname: - :param kw: - :return: - """ - _log.debug(f"AXIS FAULT: {kw}") - if kw["severity"]: - msg = f"axis {pvname} has a fault" - _log.critical(msg) - _log.critical(f"{kw}") - else: - msg = "" - self._message_critical_fault.setText(msg) - - def build_cryo_group(self, toolbox): - qutilities.add_item_to_toolbox( - toolbox, - "Cryojet", - widget_list=[ - self.get_tweaker("SAR-EXPMX:MOT_CRYO", alias="cryo", label="cryo X") - ], - ) - - def build_faststage_group(self, toolbox): - qutilities.add_item_to_toolbox(toolbox,"Fast Stage", - widget_list=[ - # self.get_tweaker('SAR-EXPMX:MOT_BLGT', alias='backlight', label='backlight'), - self.get_tweaker("SAR-EXPMX:MOT_FY", alias="fast_y", label="fast Y"), - self.get_tweaker("SAR-EXPMX:MOT_FX", alias="fast_x", label="fast X"), - self.get_tweaker("SAR-EXPMX:MOT_ROT_Y",alias="omega",label="omega",tweak_min=0.001,tweak_max=180.0,), - self.get_tweaker("SAR-EXPMX:MOT_CX", alias="base_x", label="base X"), - self.get_tweaker("SAR-EXPMX:MOT_CZ", alias="base_z", label="base Z"), - ], - ) - - def build_wegde_group(self, toolbox): - qutilities.add_item_to_toolbox(toolbox,"Wedge Mover", - widget_list=[ - self.get_tweaker("SAR-EXPMX:MOT_WEDGE1", alias="wedge_1", label="wedge_1"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGE2", alias="wedge_2", label="wedge_2"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGE3", alias="wedge_3", label="wedge_3"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGE4", alias="wedge_4", label="wedge_4"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGEX", alias="wedge_x", label="wedge_x"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGEY", alias="wedge_y", label="wedge_y"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGEA", alias="wedge_a", label="wedge_a"), - self.get_tweaker("SAR-EXPMX:MOT_WEDGEB", alias="wedge_b", label="wedge_b"), - ], - ) - - def build_collimator_group(self, toolbox): - qutilities.add_item_to_toolbox(toolbox,"Collimator", - widget_list=[ - self.get_tweaker("SARES30-ESBMX1", alias="colli_x", label="colli X", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX2", alias="colli_y", label="colli Y", mtype="smaract_motor",), - ], - ) - - def build_slits_group(self, toolbox): - qutilities.add_item_to_toolbox( - toolbox, - "Slits", - widget_list=[ - self.get_tweaker("SARES30-ESBMX10", alias="slit_right", label="left", mtype="smaract_motor", ), - self.get_tweaker("SARES30-ESBMX11", alias="slit_left", label="right", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX12", alias="slit_bottom", label="bottom", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX13",alias="slit_top",label="top",mtype="smaract_motor",), - ], - ) - - def build_xeye_group(self, toolbox): - qutilities.add_item_to_toolbox( - toolbox, - "X-Ray Eye", - widget_list=[ - self.get_tweaker("SARES30-ESBMX14", alias="xeye_x", label="X", mtype="smaract_motor"), - self.get_tweaker("SARES30-ESBMX15", alias="xeye_y", label="Y", mtype="smaract_motor"), - ], - ) - - def build_posttube_group(self, toolbox): - widgets = [ - self.get_tweaker("SARES30-ESBMX4", alias="tube_usx", label="upstream X", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX6", alias="tube_usy", label="upstream Y", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX5", alias="tube_dsx", label="downstream X", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX7", alias="tube_dsy", label="downstream Y", mtype="smaract_motor",), - self.get_tweaker("SARES30-ESBMX8", alias="tube_z", label="tube Z", mtype="smaract_motor"), - ] - c = QWidget() - c.setLayout(QGridLayout()) - but = QPushButton("post tube out") - but.clicked.connect(lambda m, dir="out": self.move_post_tube(dir)) - c.layout().addWidget(but, 0, 0) - but = QPushButton("post tube in") - but.clicked.connect(lambda m, dir="in": self.move_post_tube(dir)) - c.layout().addWidget(but, 0, 1) - widgets.append(c) - - label = "Post Sample Tube" - block = QWidget() - block.setAccessibleName(label) - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - for w in widgets: - block.layout().addWidget(w) - - block.layout().addWidget(self.build_post_tube_tandem_tweaker()) - block.layout().addStretch() - toolbox.addItem(block, label) - - def build_post_tube_tandem_tweaker(self): - block = QWidget() - block.setAccessibleName("post tube tamden") - block.setContentsMargins(0, 0, 0, 0) - block.setLayout(QVBoxLayout()) - c = QWidget() - glay = QGridLayout() - c.setLayout(glay) - - b = QWidget() - blay = QHBoxLayout() - b.setLayout(blay) - blay.addWidget(QLabel("Tweak (mm)")) - self._post_tandem_tweak_val = QLineEdit("1.0") - blay.addWidget(self._post_tandem_tweak_val) - glay.addWidget(b, 0, 0, 1, 2) - - icon = qtawesome.icon("material.arrow_back") - but = QPushButton(icon, "") - but.clicked.connect(lambda m, dir="x-pos": self.move_post_tube(dir)) - glay.addWidget(but, 1, 0) - - # c.setLayout(glay) - icon = qtawesome.icon("material.arrow_forward") - but = QPushButton(icon, "") - but.clicked.connect(lambda m, dir="x-neg": self.move_post_tube(dir)) - glay.addWidget(but, 1, 1) - - # c.setLayout(glay) - icon = qtawesome.icon("material.arrow_downward") - but = QPushButton(icon, "") - but.clicked.connect(lambda m, dir="down": self.move_post_tube(dir)) - glay.addWidget(but, 2, 0) - - # c.setLayout(glay) - icon = qtawesome.icon("material.arrow_upward") - but = QPushButton(icon, "") - but.clicked.connect(lambda m, dir="up": self.move_post_tube(dir)) - glay.addWidget(but, 2, 1) - - block.layout().addWidget(c) - return block - - def assert_post_tube_position(self, pos): - x_up = settings.value("post_sample_tube/x_up", 1e10, type=float) - y_up = settings.value("post_sample_tube/y_up", 1e10, type=float) - x_down = settings.value("post_sample_tube/x_down", 1e10, type=float) - y_down = settings.value("post_sample_tube/y_down", 1e10, type=float) - dx = settings.value("post_sample_tube/dx", 1e10, type=float) - dy = settings.value("post_sample_tube/dy", 1e10, type=float) - tz_in = settings.value("post_sample_tube/z_in", 1e10, type=float) - tz_out = settings.value("post_sample_tube/z_out", 1e10, type=float) - - if (x_up + y_up + x_down + y_down + dx + dy + tz_in + tz_out) > 1e9: - raise Exception("miscofingured positions for post-sample tube") - - usy = self.tweakers["tube_usy"] - dsy = self.tweakers["tube_dsy"] - usx = self.tweakers["tube_usx"] - dsx = self.tweakers["tube_dsx"] - tbz = self.tweakers["tube_z"] - - if pos == "in": - yu = y_up - xu = x_up - yd = y_down - xd = x_down - z = tz_in - elif pos == "out": - yu = y_up + dy - xu = x_up + dx - yd = y_down + dy - xd = x_down + dx - z = tz_out - - app_utils.assert_tweaker_positions([ - (usy, yu, 0.1), - (dsy, yd, 0.1), - (usx, xu, 0.1), - (dsx, xd, 0.1), - (tbz, z, 0.1),],timeout=2.0, - ) - - def move_post_tube(self, dir): - try: - tandem_twv = float(self._post_tandem_tweak_val.text()) - except: - tandem_twv = 1.0 - - x_up = settings.value("post_sample_tube/x_up") - y_up = settings.value("post_sample_tube/y_up") - x_down = settings.value("post_sample_tube/x_down") - y_down = settings.value("post_sample_tube/y_down") - dx = settings.value("post_sample_tube/dx") - dy = settings.value("post_sample_tube/dy") - tz_in = settings.value("post_sample_tube/z_in") - tz_out = settings.value("post_sample_tube/z_out") - if x_up is None: - msg = "SwissMX *POST-SAMPLE-TUBE* configuration is incomplete!!!" - _log.warning(msg) - QMessageBox.warning(self, "post tube not configured", msg) - return - x_up = float(x_up) - y_up = float(y_up) - x_down = float(x_down) - y_down = float(y_down) - dx = float(dx) - dy = float(dy) - tz_in = float(tz_in) - tz_out = float(tz_out) - - usy = self.tweakers["tube_usy"] - dsy = self.tweakers["tube_dsy"] - - usx = self.tweakers["tube_usx"] - dsx = self.tweakers["tube_dsx"] - tube_z = self.tweakers["tube_z"] - - if dir == "in": - _log.info("move post sample tube in") - usy.move_motor_to_position(y_up) - dsy.move_motor_to_position(y_down) - usx.move_motor_to_position(x_up) - dsx.move_motor_to_position(x_down) - try: - app_utils.assert_tweaker_positions([ - (usy, y_up, 0.1), - (dsy, y_down, 0.1), - (usx, x_up, 0.1), - (dsx, x_down, 0.1), ],timeout=10.0, - ) - except app_utils.PositionsNotReached as e: - _log.warning("failed to move post sample tube {}".format(dir)) - _log.warning(e) - QMessageBox.warning(self, "failed to move post sample tube XY {in}", "failed to move post sample tube XY {in}",) - raise - tube_z.move_motor_to_position(tz_in, wait=True) - try: - app_utils.assert_tweaker_positions([(tube_z, tz_in, 0.1)]) - except app_utils.PositionsNotReached as e: - _log.warning("failed to move post sample tube Z {in}") - _log.warning(e) - QMessageBox.warning(self, "failed to move post sample tube Z {in}", "failed to move post sample tube Z {in}",) - raise - - elif dir == "out": - _log.info("move post sample tube out") - tube_z.move_motor_to_position(tz_out, wait=True) - try: - app_utils.assert_tweaker_positions([(tube_z, tz_out, 0.1)]) - except app_utils.PositionsNotReached as e: - _log.warning("failed to move post sample tube {out}") - _log.warning(e) - QMessageBox.warning(self,"failed to move post sample tube Z {out}","failed to move post sample tube Z {out}",) - raise - usy.move_motor_to_position(y_up + dy) - dsy.move_motor_to_position(y_down + dy) - usx.move_motor_to_position(x_up + dx) - dsx.move_motor_to_position(x_down + dx) - try: - app_utils.assert_tweaker_positions([ - (usy, y_up + dy, 0.1), - (dsy, y_down + dy, 0.1), - (usx, x_up + dx, 0.1), - (dsx, x_down + dx, 0.1), ], timeout=10.0, - ) - except app_utils.PositionsNotReached as e: - _log.warning("failed to move post sample tube {}".format(dir)) - _log.warning(e) - QMessageBox.warning(self,"failed to move post sample tube XY {out}","failed to move post sample tube XY {out}",) - raise - elif dir == "x-pos": - _log.info("tamdem move post sample tube X-pos by {} mm".format(tandem_twv)) - usx.move_relative(tandem_twv) - dsx.move_relative(tandem_twv) - elif dir == "x-neg": - _log.info("tamdem move post sample tube X-neg {} mm".format(tandem_twv)) - usx.move_relative(-tandem_twv) - dsx.move_relative(-tandem_twv) - elif dir == "up": - _log.info("tamdem move post sample tube UP {} mm".format(tandem_twv)) - usy.move_relative(tandem_twv) - dsy.move_relative(tandem_twv) - elif dir == "down": - _log.info("tamdem move post sample tube DOWN {} mm".format(tandem_twv)) - usy.move_relative(-tandem_twv) - dsy.move_relative(-tandem_twv) - - def get_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None, **kwargs): - if mtype == "epics_motor": - m = MotorTweak() - else: - m = SmaractMotorTweak() - #m.connect_motor(pv, label, **kwargs) #ZAC: orig. code - self.tweakers[alias] = m - return m - - def add_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None): - if layout is None: - layout = self._tweak_container.layout() - if mtype == "epics_motor": - m = MotorTweak() - else: - m = SmaractMotorTweak() - layout.addWidget(m) - #m.connect_motor(pv, label)#ZAC: orig. code - self.tweakers[alias] = m - - def done_sliding(self): - print("done sliding at {}".format(self.slider_fast_x.value())) - - @pyqtSlot() - def saveSampleCameraScreenshot(self): - outf = self.get_screenshot_filename() - _log.info("saving original clean screenshot: {}".format(outf)) - try: - sample_camera.saveimage(outf) - except Exception as e: - _log.warning(e) - QMessageBox.warning(self, "Screenshot: failed to save image", "Failed to save screenshot!") - - @pyqtSlot() - def saveSampleCameraScreenshotView(self): - outf = self.get_screenshot_filename() - _log.info("saving view screenshot: {}".format(outf)) - - exporter = pg.exporters.ImageExporter(self.vb) - - # set export parameters if needed - exporter.parameters()["width"] = 2000 # (note this also affects height parameter) - - # save to file - try: - exporter.export(outf) - except Exception as e: - _log.warning(e) - QMessageBox.warning(self, "Screenshot: failed to save viewer image", "Failed to save screenshot of viewer!",) - - def get_screenshot_filename(self): - global folders - _log.info("taking screenhot") - prefix = folders.prefix - folder = folders.res_folder - base = time.strftime("{}_%Y%m%d_%H%M%S.png".format(prefix)) - if not os._exists(folder): - try: - os.makedirs(folder, 0o750, exist_ok=True) - except: - msg = "Failed to create folder: {}".format(folder) - _log.warning(msg) - QMessageBox.warning(self, "Screenshot: failed to create folder", "Failed to create output folder for screenshot!\n\n\tScreenshot not taken!",) - raise - outf = os.path.join(folder, base) - return outf - - def daq_grid_add_grid(self, gx=None, gy=None): - grid_index = len(self._grids) - if gx in (False, None): - gx, gy = self.getFastX(), self.getFastY() - xstep = self._sb_grid_x_step.value() - ystep = self._sb_grid_y_step.value() - xoffset = self._sb_grid_x_offset.value() - yoffset = self._sb_grid_y_offset.value() - grid = Grid( x_step=xstep, y_step=ystep, x_offset=xoffset, y_offset=yoffset, gonio_xy=(gx, gy), grid_index=grid_index, parent=self,) - self._grids.append(grid) - grid.calculate_gonio_xy() - grid.sigRemoveRequested.connect(lambda g=grid: self.remove_grid(g)) - self.vb.addItem(grid) - - def daq_grid_remove_all(self): - for grid in self._grids: - grid.sigRemoveRequested.emit(grid) # ugly hack - self._grids = [] - - def grids_pause_stage_tracking(self): - for grid in self._grids: - grid.disable_stage_tracking() - - def grids_start_stage_tracking(self): - for grid in self._grids: - grid.enable_stage_tracking() - - def remove_grid(self, grid): - self.vb.removeItem(grid) - self._grids.remove(grid) - - - def daq_grid_update(self, grid_index): - try: - grid = self._grids[grid_index] - except: - print("grid index not yet there") - return - points = grid.get_grid_targets() - num_points = len(points) - etime = float(settings.value("exposureTime")) - doc = f"grid_{grid_index} = [" - for n, pos in enumerate(points): - x, y = pos - doc += "[{:8.3f}, {:8.3f}],\n".format(x, y) - doc += "]" - self._grid_inspect_area.setPlainText(doc) - m = "Number of points: {}\nEstimated Time: {:.1f} minutes".format(num_points, num_points * etime / 60.) - self._label_grid_parameters.setText(m) - - def daq_embl_collect_points(self): - coords = self._embl_module.coords - points = [[x, y] for x, y, bx, bz, o in coords] - points = np.array(points) - method = "trajectory" - xp = (1000 * points).astype(int) # microns then round int - params = (xp[:, 0].tolist(), xp[:, 1].tolist()) - self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) - - def daq_prelocated_collect_points(self): - points = [] - data = self.prelocationModule.get_collection_targets() - for n, cc in enumerate(data): - is_fiducial, gx, gy, cx, cy, ox, oy = cc - points.append([gx, gy]) - points = np.array(points) - method = "trajectory" - xp = (1000 * points).astype(int) # microns then round int - params = (xp[:, 0].tolist(), xp[:, 1].tolist()) - self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) - - def daq_grid_collect_grid(self): - grid = self._grids[0] # FIXME one grid at a time only - points = np.array(grid.get_grid_targets()) - method = "grid" - params = grid._grid_dimensions - # params = ([grid._grid_dimensions[0]], [grid._grid_dimensions[1]]) # FIXME something wrong here< - self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) - - def daq_grid_findxtals(self): - feature_size = self._sb_findxtals_feature_size.value() - image = sample_camera.get_image() - findObj(-image, objSize=feature_size, viz=1) - - def check_jungfrau_save(self) -> bool: - if jungfrau_detector.is_running_detector(): - saveRaw = jungfrau_detector.is_saving_data() - - if not saveRaw: - box = QMessageBox() - box.setText("Jungfrau save data disabled!") - box.setInformativeText("Jungfrau save data is disabled!") - box.setIcon(QMessageBox.Warning) - box.setDetailedText("Choose to abort, enable and continue, or continue without saving raw data") - btContinue = box.addButton("Continue", QMessageBox.YesRole) - btAbort = box.addButton("OMG! Abort", QMessageBox.NoRole) - btEnable = box.addButton("Enable save and continue", QMessageBox.YesRole) - box.exec_() - ans = box.clickedButton() - if ans == btEnable: - jungfrau_detector.set_save_raw(True) - return True - elif ans == btAbort: - _log.info("not doing helical scan") - return False - return True - return True - - def daq_collect_points(self, points, visualizer_method, visualizer_params): - task = self.active_task() - XDIR = -1 - - folders.make_if_needed() - - if ( option(ACTIVATE_PULSE_PICKER) and not jungfrau_detector.is_running_detector()): - if QMessageBox.No == QMessageBox.question(self, "X-rays but no Jungfrau", - "X-rays will be used bu the Jungfrau will not run.\n\n\tContinue?",): - _log.warning("user forgot to turn on the jungfrau") - return - - if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): - self.escape_goToDataCollection() - - ntrigger = len(points) - points *= 1000 # delta tau uses micrometers - points[:, 0] *= XDIR # fast X axis is reversed - - etime = settings.value("exposureTime", type=float) - vscale = settings.value(DELTATAU_VELOCITY_SCALE, 1.0, type=float) - sort_points = option(DELTATAU_SORT_POINTS) - - # sync_mode : default=2 - # 0 : no sync at all - # 1 : synchronize start - # 2 : synchronize start and adapt motion speed - # this function generates the code blocks: - # self.sync_wait and self.sync_run - # sync_wait can be put in the program to force a timing sync - # sync_run are the commands to run the whole program - # sync_flag if not using jungfrau =1 otherwise =0 - # D.O. shapepath.meta.update(sync_mode=2, sync_flag=1) - shapepath.meta.update(sync_mode=0, sync_flag=0) - maxacq_points = 116508 - samp_time = 0.0002 # s - overhead_time = 0.1 - acq_per = int(np.ceil((etime * ntrigger + overhead_time) / (maxacq_points * samp_time))) - _log.info(f"gather acquisotion period = {acq_per}") - _log.info(f"velocity scale {vscale}") - shapepath.setup_gather(acq_per=acq_per) - shapepath.setup_sync(verbose=True) - shapepath.setup_coord_trf() - shapepath.points = np.copy(points) - - if TASK_GRID == task: - width, height = visualizer_params - _log.debug(f"grid: {width} x {height}") - details_1 = [width] - details_2 = [height] - shapepath.sort_points(xy=False, grp_sz=height) - elif task in (TASK_PRELOCATED, TASK_EMBL): - if sort_points: - shapepath.sort_points() - self.daq_method_prelocated_remove_markers() - details_1, details_2 = visualizer_params - - shapepath.setup_motion( - mode=3, # 1 = bad pvt 3 = pft (pvt via inverse fourier transform) - pt2pt_time=etime * 1000., - fnPrg=folders.get_prefixed_file("_program.prg"), - scale=vscale, # velocity at target position scaling: 1=optimal speed, 0=zero speed - dwell=10, # milli-seconds wait - ) - shapepath.run() - - self.qprogress = QProgressDialog(self) - self.qprogress.setRange(0, 0) - self.qprogress.setLabelText("Acquiring GRID") - self.qprogress.setCancelButtonText("Abort Acquisition") - self.qprogress.canceled.connect(self.complete_daq) - self.qprogress.setWindowModality(Qt.WindowModal) - self.qprogress.setAutoClose(True) - self.qprogress.show() - - sequencer_steps = [lambda: self.grids_pause_stage_tracking()] - - if jungfrau_detector.is_running_detector(): - if not self.check_jungfrau_save(): - # user aborted run from save data dialog - return - - n_frames = ntrigger - uid = settings.value(EXPERIMENT_UID, type=int) - backend_extras = self.jungfrau.get_parameters() - backend_extras.update( - { - "swissmx_trajectory_method": visualizer_method, - "swissmx_trajectory_details_1": details_1, - "swissmx_trajectory_details_2": details_2, - } - ) - jungfrau_detector.set_number_of_frames(n_frames) - jungfrau_detector.set_data_owner_uid(uid) - sequencer_steps.extend( - [ - lambda: jungfrau_detector.configure( - n_frames=n_frames, - outfile=folders.prefix, - outdir=folders.raw_folder, - uid=uid, - backend_extras=backend_extras, - ), - lambda: jungfrau_detector.arm(), - ] - ) - - sequencer_steps.append(lambda: shapepath.wait_armed()) - if option(ACTIVATE_PULSE_PICKER): - sequencer_steps.append(lambda: pulsePicker.open()) - - # if settings.value("scanning/trigger_microscope_camera", type=bool): - # sample_camera.switch_to_trigger(True) - - sequencer_steps.append(lambda: shapepath.trigger(wait=0.5)) - - def shapepath_progress(): - while True: - p = shapepath.progress() - if p < 0: - break - time.sleep(0.1) - self.qprogress.setLabelText(f"Acquiring GRID {p:.0f} / {ntrigger}") - _log.warning(f"motion complete!") - # sample_camera.stop_camera() - # sample_camera.switch_to_trigger(False) - # sample_camera.save_buffer_series(folders.prefix) - - sequencer_steps.append(shapepath_progress) - - if option(ACTIVATE_PULSE_PICKER): - sequencer_steps.append(lambda: pulsePicker.close()) - - sequencer_steps.append(lambda: jungfrau_detector.wait_finished()) - - sequencer_steps.append(lambda: self.grids_start_stage_tracking()) - - self.sequencer = Sequencer(steps=sequencer_steps) - self._thread = QThread() - self._thread.setObjectName("acquisition_thread") - self.sequencer.moveToThread(self._thread) - self.sequencer.finished.connect(self.daq_collect_points_wrapup) - self._thread.started.connect(self.sequencer.run_sequence) - self._thread.start() - - def run_steps(self, steps, title, at_end=None, cancelable=False): - dlg = QProgressDialog(self) - dlg.setWindowTitle(title) - dlg.setWindowModality(Qt.WindowModal) - dlg.setMinimumDuration(0) - if not cancelable: - dlg.setCancelButton(None) - dlg.setRange(0, 0) - dlg.setLabelText(f"{title}
") - dlg.setAutoClose(True) - dlg.show() - dlg.setValue(random.randint(1, 20)) - class Runner(QObject): - finito = pyqtSignal() - timeoutExpired = pyqtSignal() - errorHappened = pyqtSignal(str) - result = pyqtSignal(str) - - def __init__(self, step_to_run): - super().__init__() - self.step = step_to_run - self.exception = None - self.done = False - - def run(self): - try: - self.step() - except Exception as e: - _log.debug(" +> step exception") - self.exception = str(e) - self.errorHappened.emit(str(e)) - self.finito.emit() - - - for n, step in enumerate(steps): - _log.info(f"running step {step.title}") - dlg.setLabelText(f"{title}
{step.title}") - dlg.setValue(n) - thread = QThread() - runner = Runner(step) - runner.moveToThread(thread) - thread.started.connect(runner.run) - runner.finito.connect(thread.quit) - thread.start() - while thread.isRunning(): - dlg.setValue(random.randint(1, 20)) - time.sleep(0.01) - if dlg.wasCanceled(): - # FIXME ensure we abort the running thread - break - - if dlg.wasCanceled(): - break - if runner.exception is not None: - QMessageBox.critical(self, step.title, str(runner.exception)) - break - - if dlg.wasCanceled(): - _log.error(f"sequence {title} was cancelled by user") - raise AcquisitionAbortedException(f"sequence {title} was cancelled by user") - - if at_end is not None: - at_end() - dlg.reset() - - def daq_collect_points_wrapup(self): - self.qprogress.reset() - if self._thread.isRunning(): - self._thread.quit() - shapepath.gather_upload(os.path.join(folders.res_folder, folders.prefix + ".npz")) - - if option(DELTATAU_SHOW_PLOTS): - dp = DebugPlot(shapepath) - dp.plot_gather(mode=1) - pyplot.show() - - if TASK_PRELOCATED == self.active_task(): - self.daq_method_prelocated_update_markers() - - if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): - self.escape_goToSampleAlignment() - - sequence = {"delta tau program": shapepath.prg, "points": shapepath.points.tolist(), "timestamp": tdstamp(),} - - sfname = folders.get_prefixed_file("_ScanInfo.json") - try: - with open(sfname, "w") as sf: - _log.info("writing scan info into: {}".format(sfname)) - sf.write(json.dumps(sequence)) - except: - _log.warning(f"failed to write scan info to {sfname}") - - self.re_connect_collect_button() - jungfrau_detector.abort() - self.increaseRunNumberRequest.emit() - - def daq_collect_update_inspect(self, msg): - te = self._inspect - m = te.toPlainText() - te.setPlainText(m + msg + "\n") - te.verticalScrollBar().setValue(te.verticalScrollBar().maximum()) - - def daq_helical_collect(self): - """[ - [{ - 0: (0.0, 1.238401694734829, 3.527, 0.936, 0.001), - 120: (0.0, 1.3890393596274455, -1.957242824091597, -0.5639999999999998, 120.001), - 240: (0.0, 1.3866130349657206, -1.1642619618562273, 3.105, 240.001) - }, - { - 0: (0.0, 1.238401694734829, 3.527, 0.936, 0.001), - 120: (0.0, 1.3890393596274455, -1.957242824091597, -0.5639999999999998, 120.001), - 240: (0.0, 1.3866130349657206, -1.1642619618562273, 3.105, 240.001) - }] - ] - """ - _log.info("executing collection") - htab = self._helical_scan_table - num_h = htab.scanHorizontalCount() - num_v = htab.scanVerticalCount() - - if ( settings.value(ACTIVATE_PULSE_PICKER) and not jungfrau_detector.is_running_detector()): - if QMessageBox.No == QMessageBox.question(self, "X-rays but no Jungfrau", - "X-rays will be used bu the Jungfrau will not run.\n\n\tContinue?",): - _log.warning("user forgot to turn on the jungfrau") - return - - if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): - self.escape_goToDataCollection() - - folders.make_if_needed() - - data = htab.get_data() - _log.debug(data) - start, end = data[0] - FX, FY, BX, BZ, O = range(5) - x = ( - (-1000 * start[0][BX], -1000 * start[120][BX], -1000 * start[240][BX]), - (-1000 * end[0][BX], -1000 * end[120][BX], -1000 * end[240][BX]), - ) - y = (1000 * start[0][FY], 1000 * end[0][FY]) - z = ( - (-1000 * start[0][BZ], -1000 * start[120][BZ], -1000 * start[240][BZ]), - (-1000 * end[0][BZ], -1000 * end[120][BZ], -1000 * end[240][BZ]), - ) - - if jungfrau_detector.is_running_detector(): - if not self.check_jungfrau_save(): - return - saveRaw = jungfrau_detector.is_saving_data() - - _log.debug(f"x = {x}") - _log.debug(f"y = {y}") - _log.debug(f"z = {z}") - - oscillationAngle = settings.value("oscillationAngle", type=float) - exposureTime = 1000 * settings.value("exposureTime", type=float) - blastRadius = settings.value("blastRadius", type=float) - totalRange = num_v * num_h * oscillationAngle - - # sync_mode : default=2 - # 0 : no sync at all - # 1 : synchronize start - # 2 : synchronize start and adapt motion speed - # this function generates the code blocks: - # self.sync_wait and self.sync_run - # sync_wait can be put in the program to force a timing sync - # sync_run are the commands to run the whole program - # sync_flag if not using jungfrau =1 otherwise =0 - # D.O. helical.meta.update(sync_mode=2, sync_flag=1) - helical.meta.update(sync_mode=0, sync_flag=0) - helical.calcParam(x=x, y=y, z=z) - helical.setup_gather() - helical.setup_sync() - helical.setup_coord_trf() - mode = 1 - hRng = (-blastRadius * num_h, blastRadius * num_h) - w_start = 1000 * htab.startAngle() - wRng = (w_start, w_start + (totalRange * 1000)) - _log.info( - f"helical params mode={mode}, cnthor={num_h}, cntvert={num_v}, hrng={hRng}, wrng={wRng}" - ) - helical.setup_motion( - mode=mode, - cntHor=num_h, - cntVert=num_v, - hRng=hRng, - wRng=wRng, - pt2pt_time=exposureTime, - ) # hRng in micrometers - helical.run() - try: - with open(folders.get_prefixed_file("_helical_debug.cmd"), "w") as fd: - fd.write("calcParam(x={}, y={}, z={})".format(x, y, z)) - except: - pass - - self.qprogress = QProgressDialog(self) - self.qprogress.setRange(0, 0) - self.qprogress.setLabelText("Acquiring HELICAL") - self.qprogress.setCancelButtonText("Abort Acquisition") - self.qprogress.canceled.connect(self.complete_daq) - self.qprogress.setWindowModality(Qt.WindowModal) - self.qprogress.setAutoClose(True) - self.qprogress.show() - - sequencer_steps = [lambda: self.grids_pause_stage_tracking()] - - n_frames = num_h * num_v - if jungfrau_detector.is_running_detector(): - uid = settings.value(EXPERIMENT_UID, type=int) - backend_extras = self.jungfrau.get_parameters() - backend_extras.update( - { - "swissmx_trajectory_method": "grid", - "swissmx_trajectory_details_1": [-num_h], - "swissmx_trajectory_details_2": [num_v], - } - ) - - jungfrau_detector.set_number_of_frames(n_frames) - jungfrau_detector.set_data_owner_uid(uid) - sequencer_steps.extend( - [ - lambda: jungfrau_detector.configure( - n_frames=n_frames, - outfile=folders.prefix, - outdir=folders.raw_folder, - uid=uid, - backend_extras=backend_extras, - ), - lambda: jungfrau_detector.arm(), - ] - ) - - sequencer_steps.append(lambda: helical.wait_armed()) - if settings.value(ACTIVATE_PULSE_PICKER): - sequencer_steps.extend([lambda: pulsePicker.open(), lambda: pend_event(0.1)]) - - sequencer_steps.append(lambda: helical.trigger()) - - def motion_progress(): - while True: - p = helical.progress() - if p < 0: - break - time.sleep(0.1) - self.qprogress.setLabelText(f"Acquiring HELICAL {p:.0f} / {n_frames}") - _log.warning(f"helical motion complete!") - - sequencer_steps.append(motion_progress) - - if settings.value(ACTIVATE_PULSE_PICKER): - sequencer_steps.append(lambda: pulsePicker.close()) - - sequencer_steps.append(lambda: self.grids_start_stage_tracking()) - - self.sequencer = Sequencer(steps=sequencer_steps) - self._thread = QThread() - self._thread.setObjectName("acquisition_thread") - self.sequencer.moveToThread(self._thread) - self.sequencer.finished.connect(self.daq_helical_collect_wrapup) - self._thread.started.connect(self.sequencer.run_sequence) - self._thread.start() - - def daq_helical_collect_wrapup(self): - try: - self.qprogress.reset() - except: - pass - if self._thread.isRunning(): - self._thread.quit() - helical.gather_upload(os.path.join(folders.res_folder, folders.prefix + ".npz")) - - self.re_connect_collect_button() - jungfrau_detector.abort() - if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): - self.escape_goToSampleAlignment() - self.increaseRunNumberRequest.emit() - - if option(DELTATAU_SHOW_PLOTS): - hsgui = HelicalScanGui(helical) - hsgui.interactive_anim() - - def complete_daq(self): - _log.info("daq completed: cleaning up") - try: - self.qprogress.reset() - except: - pass - try: - if self._thread.isRunning(): - self._thread.quit() - except: - pass - finally: - self.abort_measurement() - self.re_connect_collect_button() - - def build_daq_methods_grid_tab(self): - self._grids = [] - tab = self._tab_daq_method_grid - layout = tab.layout() # gridlayout - self._sb_grid_x_step.setValue(30.0) - self._sb_grid_y_step.setValue(30.0) - self._bt_add_grid.clicked.connect(self.daq_grid_add_grid) - self._bt_remove_all_grids.clicked.connect(self.daq_grid_remove_all) - self._find_targets_from_microscope_image.clicked.connect( - self.daq_grid_findxtals - ) - self.addGridRequest.connect(self.daq_grid_add_grid) - self.gridUpdated.connect(self.daq_grid_update) - - def re_connect_collect_button(self, callback=None, **kwargs): - _log.debug("re_connect_collect_button() {} => {}".format(callback, kwargs)) + dx = np.cos(omega * np.pi / 180) * (x - bx) / ppm + dy = -(y - by) / ppm + _log.info("moving fast x/y to {:.3f}, {:.3f}".format(dx, dy)) + fx_motor.move_relative(dx) + fy_motor.move_relative(dy) + self.fast_dx_position.emit(dx) + self.fast_dy_position.emit(dy) return - # button = self._button_collect - # if callback is None: - # callback = self.execute_collection - # button.setAccessibleName("collect_button") - # kwargs["label"] = "Collect" - # - # try: - # button.disconnect() - # finally: - # button.clicked.connect(callback) - # - # if "accessibleName" in kwargs: - # button.setAccessibleName(kwargs["accessibleName"]) - # - # if "label" in kwargs: - # button.setText(kwargs["label"]) - # self.load_stylesheet() - def collect_abort_grid(self): - self._is_aborted = True + # CTRL-Click always just moves Z + if ctrl and not (shft or alt): + mm = np.sign(y - by) * pixel_dist / 10000. + _log.debug("moving Z stage by {:.3f} mm".format(mm)) + bz_motor.move_relative(mm) + return + + if task in (TASK_SETUP_PPM_CALIBRATION, TASK_SETUP_BEAM_CENTER): + if ctrl and self._ppm_calibration.isChecked(): + if self._ppm_click is not None: + feature_dist = self._ppm_feature_size_spinbox.value() / 1000. + b = self._ppm_click + pixel_dist = np.linalg.norm(a - b) + ppm = pixel_dist / feature_dist + _log.debug("calib save a {}".format(a)) + _log.debug("calib save b {}".format(b)) + _log.debug(" feature {}".format(feature_dist)) + _log.debug("a -> b pixels {}".format(pixel_dist)) + _log.debug(" ppm({}) {}".format(zoom_level, pixel_dist)) + + self._zoom_to_ppm[zoom_level] = ppm + settings.setValue(CAMERA_ZOOM_TO_PPM, self._zoom_to_ppm) + self._ppm_click = None + _log.debug("ppm data so far {}".format(self._zoom_to_ppm)) + else: + _log.debug("calib save b {}".format(a)) + self._ppm_click = a + return + elif task == TASK_SETUP_BEAM_CENTER: + if ctrl and not (shft or alt): + _log.info("beam mark add: zoom {} => {:.1f} {:.1f}".format(zoom_level, x, y)) + self.append_to_beam_markers(x, y, zoom_level) + else: + _log.warning("click combination not available") + elif task == TASK_POINT_SHOOT: + pass + elif task == TASK_GRID: + if shft and not (ctrl or alt): + self.addGridRequest.emit(fx, fy) + elif task == TASK_PRELOCATED: + if ctrl and shft: + omegacos = option(DELTATAU_OMEGACOS) + if omegacos: + fx = fx / np.cos(omega * np.pi / 180) + _log.info( + "selectin fiducial (omegacos): {:.3f}, {:.3f}".format(fx, fy) + ) + else: + _log.info("selectin fiducial: {:.3f}, {:.3f}".format(fx, fy)) + self.fiducialPositionSelected.emit(x, y, fx, fy) + elif shft and alt: + self.appendPrelocatedPosition.emit(x, y, fx, fy) + elif task == TASK_HELICAL: + _log.info("no action associated ") + elif task == TASK_EMBL: + pass + else: + pass + + def get_beam_mark_on_camera_xy(self): + z = qoptic_zoom.get_position() + try: + bx = self.beamx_fitter(z) + by = self.beamy_fitter(z) + except: + bx, by = 500, 500 + return (bx, by) + + def safe_backlight_move(self, pos): + # any move of backlight requires post sample tube out + try: + self.assert_post_tube_position(pos="out") + except: + self.move_post_tube("out") + backlight.move(pos) + + def escape_run_steps(self, steps, title): + with pg.ProgressDialog(title, 0, len(steps)) as dlg: + for step in steps: + step() + dlg += 1 + if dlg.wasCanceled(): + raise TransitionAborted("ABORTED" + title) + + def escape_goToSampleExchange(self): + self._escape_current_state = "busy" + steps = [] + if option(CRYOJET_MOTION_ENABLED): + steps.append(lambda: self.move_cryojet_nozzle("out")) + steps.extend( + [ + lambda: self.move_post_tube("out"), + lambda: backlight.move("out", wait=True), + lambda: self.move_collimator("out"), + ] + ) + self.escape_run_steps(steps, "Transitioning to Sample Exchange") + self._escape_current_state = "ManualSampleExchange" + + def escape_goToSampleAlignment(self): + self._escape_current_state = "busy" + + steps = [ + # lambda: sample_selection.tell.set_current(30.0), + lambda: self.move_collimator("ready") + ] + 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")]) + self.escape_run_steps(steps, "Transitioning to Sample Alignment") + self._escape_current_state = "SampleAlignment" + + def escape_goToDataCollection(self): + self._escape_current_state = "busy" + steps = [ + # lambda: sample_selection.tell.set_current(30.0), + lambda: backlight.move("out", assert_positions=True), + lambda: self.move_post_tube("in"), + lambda: self.move_collimator("in"), + ] + if option(CRYOJET_MOTION_ENABLED): + steps.extend([lambda: self.move_cryojet_nozzle("in")]) + self.escape_run_steps(steps, "Transitioning to Data Collection") + self._escape_current_state = "DataCollection" + + def move_gonio_to_position(self, fx, fy, bx, bz, omega): + self.tweakers["fast_x"].motor.move(fx, wait=False, ignore_limits=True) + self.tweakers["fast_y"].motor.move(fy, wait=False, ignore_limits=True) + self.tweakers["base_x"].motor.move(bx, wait=False, ignore_limits=True) + self.tweakers["base_z"].motor.move(bz, wait=False, ignore_limits=True) + self.tweakers["omega"].motor.move(omega, wait=False, ignore_limits=True) + + def get_gonio_motors(self, as_json=False): + if as_json: + return { + "fast_x": self.tweakers["fast_x"].motor, + "fast_y": self.tweakers["fast_y"].motor, + "base_x": self.tweakers["base_x"].motor, + "base_z": self.tweakers["base_z"].motor, + "omega": self.tweakers["omega"].motor, + } + else: + return ( + self.tweakers["fast_x"].motor, + self.tweakers["fast_y"].motor, + self.tweakers["base_x"].motor, + self.tweakers["base_z"].motor, + self.tweakers["omega"].motor, + ) + + def get_gonio_tweakers(self): + return ( + self.tweakers["fast_x"], + self.tweakers["fast_y"], + self.tweakers["base_x"], + self.tweakers["base_z"], + self.tweakers["omega"], + ) + + def get_gonio_positions(self, as_json: bool = False): + fx, fy, cx, cz, omega = ( + self.tweakers["fast_x"].motor, + self.tweakers["fast_y"].motor, + self.tweakers["base_x"].motor, + self.tweakers["base_z"].motor, + self.tweakers["omega"].motor, + ) + + a, b, c, d, e = ( + fx.get_position(), + fy.get_position(), + cx.get_position(), + cz.get_position(), + omega.get_position(), + ) + if as_json: + return {"fx": a, "fy": b, "bx": c, "bz": d, "omega": e} + else: + return (a, b, c, d, e) + + def escape_goToTellMountPosition(self): + self.move_gonio_to_mount_position() + self.lock_goniometer() + + def move_gonio_to_mount_position(self, offset: float = 0.0): + fx, fy, cx, cz, omega = self.get_gonio_motors() + bmark = "bookmark_0" + try: + t_fx = float(settings.value(bmark + "/mount_fx")) + t_fy = -offset + float(settings.value(bmark + "/mount_fy")) + t_cx = float(settings.value(bmark + "/mount_cx")) + t_cz = float(settings.value(bmark + "/mount_cz")) + t_omega = float(settings.value(bmark + "/mount_omega")) + except: + raise IncompleteConfiguration("TELL sample changer mount position is not configured!!!") + fx.move(t_fx, wait=True, ignore_limits=True) + fy.move(t_fy, wait=True, ignore_limits=True) + cx.move(t_cx, wait=True, ignore_limits=True) + cz.move(t_cz, wait=True, ignore_limits=True) + omega.move(t_omega, wait=True, ignore_limits=True) + app_utils.assert_motor_positions( + [ + (fx, t_fx, 0.01), + (fy, t_fy, 0.01), + (cx, t_cx, 0.01), + (cz, t_cz, 0.01), + (omega, t_omega, 0.01), + ] + ) + self.escape_goToSampleExchange() + + def lock_goniometer(self): + # tell.set_in_mount_position(True) + res = QMessageBox.question(self, "", "Mount a sample from console and click ok once the sample is mounted.", QMessageBox.Ok, QMessageBox.Ok,) + res = QMessageBox.question(self, "", "Is the sample is mounted?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) + if res in (QMessageBox.Yes, QMessageBox.No): + # tell.set_in_mount_position(False) + pass + + @pyqtSlot(int) + def saveBookmark(self, key: int): + """save a bookmark for the corresponding key is the Qt.Key_* code """ + fx, fy, cx, cz, omega = ( + self.tweakers["fast_x"].motor, + self.tweakers["fast_y"].motor, + self.tweakers["base_x"].motor, + self.tweakers["base_z"].motor, + self.tweakers["omega"].motor, + ) + bmark = "bookmark_{}".format(key) + if key == 0: + ans = QMessageBox.question(self, "Override TELL mount position", "This will overwrite the positions used for TELL MOUNTING!!!\n\n\tContinue ?",) + if ans != QMessageBox.Yes: + return + + _log.info( + "saving bookmark {}: {}, {}, {}, {}, {}".format( + bmark, + fx.get_position(), + fy.get_position(), + cx.get_position(), + cz.get_position(), + omega.get_position(), + ) + ) + settings.setValue(bmark + "/mount_fx", fx.get_position()) + settings.setValue(bmark + "/mount_fy", fy.get_position()) + settings.setValue(bmark + "/mount_cx", cx.get_position()) + settings.setValue(bmark + "/mount_cz", cz.get_position()) + settings.setValue(bmark + "/mount_omega", omega.get_position()) + + def gotoBookmark(self, key: int): + """save a bookmark for the corresponding key""" + fx, fy, cx, cz, omega = ( + self.tweakers["fast_x"].motor, + self.tweakers["fast_y"].motor, + self.tweakers["base_x"].motor, + self.tweakers["base_z"].motor, + self.tweakers["omega"].motor, + ) + bmark = "bookmark_{}".format(key) + try: + t_fx = float(settings.value(bmark + "/mount_fx")) + t_fy = float(settings.value(bmark + "/mount_fy")) + t_cx = float(settings.value(bmark + "/mount_cx")) + t_cz = float(settings.value(bmark + "/mount_cz")) + t_omega = float(settings.value(bmark + "/mount_omega")) + except: + return + + fx.move(t_fx, wait=True, ignore_limits=True) + fy.move(t_fy, wait=True, ignore_limits=True) + cx.move(t_cx, wait=True, ignore_limits=True) + cz.move(t_cz, wait=True, ignore_limits=True) + omega.move(t_omega, wait=True, ignore_limits=True) + + def move_collimator(self, pos): + cx = self.tweakers["colli_x"] + cy = self.tweakers["colli_y"] + + x_pos = settings.value("collimator/x_in", 1e10, type=float) + y_pos = settings.value("collimator/y_in", 1e10, type=float) + dx = settings.value("collimator/dx", 1e10, type=float) + dy = settings.value("collimator/dy", 1e10, type=float) + + if 1e9 < x_pos + y_pos + dx + dy: + raise IncompleteConfiguration("COLLIMATOR configuration is incomplete!") + + _log.info("moving collimator {} to X,Y = {:.3f}, {:.3f}".format(pos, x_pos, y_pos)) + + if pos == "out": + cy.move_motor_to_position(y_pos + dy, assert_position=True) + cx.move_motor_to_position(x_pos + dx, assert_position=True) + elif pos == "in": + cx.move_motor_to_position(x_pos, assert_position=True) + cy.move_motor_to_position(y_pos, assert_position=True) + elif pos == "ready": + cx.move_motor_to_position(x_pos, assert_position=True) + cy.move_motor_to_position(y_pos + dy, assert_position=True) + else: + raise UnknownLabeledPosition("Collimator position *{}* is not known!!") + + def move_cryojet_nozzle(self, pos): + cx = self.tweakers["cryo"] + if "in" == pos.lower(): + key = CRYOJET_NOZZLE_IN + elif "out" == pos.lower(): + key = CRYOJET_NOZZLE_OUT + + to_pos = settings.value(key, 1e10, type=float) + if to_pos > 1e9: + raise IncompleteConfiguration(f"CRYOJET configuration is incomplete! Missing {key}") + cx.move_motor_to_position(to_pos, assert_position=True) + + def setup_sliders(self): + cont = self._rightmost + self._tweak_container = QWidget() + self._tweak_container.setLayout(QVBoxLayout()) + cont.layout().addWidget(self._tweak_container) + layout = self._tweak_container.layout() + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + if self._at_x06sa: + zoom_base = "ESBMX-SAMCAM" # fetura IOC by Jose Gabadinho + elif self._at_lab_eh060: + zoom_base = "/dev/ttyUSB0" # direct connection using fetura.py package + elif self._at_cristalina: + zoom_base = ("rest://pc12818.psi.ch:9999") # direct connection using fetura.py package + + self.zoombox = zoom.Zoom() + self.zoombox.configure() + self.zoombox.zoomChanged.connect(self.zoom_changed_cb) + self.zoombox.moveBacklight.connect(self.safe_backlight_move) + + layout.addWidget(self.zoombox) + + toolbox = QToolBox() + layout.addWidget(toolbox) + + self.build_faststage_group(toolbox) + # self.build_slits_group(toolbox) + self.build_collimator_group(toolbox) + self.build_posttube_group(toolbox) + self.build_xeye_group(toolbox) + self.build_cryo_group(toolbox) + + # monitor all axis for an axis fault + for key, tweaker in self.tweakers.items(): + tweaker.event_axis_fault.connect(self.axis_fault) + + self.tweakers["fast_x"].event_readback.connect(lambda alias, value, kw: self.fast_x_position.emit(value)) + self.tweakers["fast_y"].event_readback.connect(lambda alias, value, kw: self.fast_y_position.emit(value)) + + # layout.addStretch() + + def axis_fault(self, pvname, kw): + """ + swissmx - {'pvname': 'SAR-EXPMX:MOT_FY.STAT', 'value': 0, 'char_value': 'NO_ALARM', 'status': 0, 'ftype': 17, 'chid': 38980392, + 'host': 'SAR-CPPM-EXPMX1.psi.ch:5064', 'count': 1, 'access': 'read-only', 'write_access': False, 'read_access': True, + 'severity': 0, + 'timestamp': 1537867290.914189, 'precision': None, 'units': None, 'enum_strs': ('NO_ALARM', 'READ', 'WRITE', 'HIHI', 'HIGH', 'LOLO', 'LOW', 'STATE', 'COS', 'COMM', 'TIMEOUT', 'HWLIMIT', 'CALC', 'SCAN', 'LINK', 'SOFT'), 'upper_disp_limit': None, 'lower_disp_limit': None, 'upper_alarm_limit': None, 'lower_alarm_limit': None, 'lower_warning_limit': None, 'upper_warning_limit': None, 'upper_ctrl_limit': None, 'lower_ctrl_limit': None, 'nelm': 1, 'type': 'time_enum', 'typefull': 'time_enum', 'motor_field': 'STAT', 'source_field': 'STAT', 'cb_info': (1, ), 'alias': 'fast Y'} + + :param pvname: + :param kw: + :return: + """ + _log.debug(f"AXIS FAULT: {kw}") + if kw["severity"]: + msg = f"axis {pvname} has a fault" + _log.critical(msg) + _log.critical(f"{kw}") + else: + msg = "" + self._message_critical_fault.setText(msg) + + def build_cryo_group(self, toolbox): + qutilities.add_item_to_toolbox( + toolbox, + "Cryojet", + widget_list=[ + self.get_tweaker("SAR-EXPMX:MOT_CRYO", alias="cryo", label="cryo X") + ], + ) + + def build_faststage_group(self, toolbox): + qutilities.add_item_to_toolbox(toolbox,"Fast Stage", + widget_list=[ + # self.get_tweaker('SAR-EXPMX:MOT_BLGT', alias='backlight', label='backlight'), + self.get_tweaker("SAR-EXPMX:MOT_FY", alias="fast_y", label="fast Y"), + self.get_tweaker("SAR-EXPMX:MOT_FX", alias="fast_x", label="fast X"), + self.get_tweaker("SAR-EXPMX:MOT_ROT_Y",alias="omega",label="omega",tweak_min=0.001,tweak_max=180.0,), + self.get_tweaker("SAR-EXPMX:MOT_CX", alias="base_x", label="base X"), + self.get_tweaker("SAR-EXPMX:MOT_CZ", alias="base_z", label="base Z"), + ], + ) + + def build_wegde_group(self, toolbox): + qutilities.add_item_to_toolbox(toolbox,"Wedge Mover", + widget_list=[ + self.get_tweaker("SAR-EXPMX:MOT_WEDGE1", alias="wedge_1", label="wedge_1"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGE2", alias="wedge_2", label="wedge_2"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGE3", alias="wedge_3", label="wedge_3"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGE4", alias="wedge_4", label="wedge_4"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGEX", alias="wedge_x", label="wedge_x"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGEY", alias="wedge_y", label="wedge_y"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGEA", alias="wedge_a", label="wedge_a"), + self.get_tweaker("SAR-EXPMX:MOT_WEDGEB", alias="wedge_b", label="wedge_b"), + ], + ) + + def build_collimator_group(self, toolbox): + qutilities.add_item_to_toolbox(toolbox,"Collimator", + widget_list=[ + self.get_tweaker("SARES30-ESBMX1", alias="colli_x", label="colli X", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX2", alias="colli_y", label="colli Y", mtype="smaract_motor",), + ], + ) + + def build_slits_group(self, toolbox): + qutilities.add_item_to_toolbox( + toolbox, + "Slits", + widget_list=[ + self.get_tweaker("SARES30-ESBMX10", alias="slit_right", label="left", mtype="smaract_motor", ), + self.get_tweaker("SARES30-ESBMX11", alias="slit_left", label="right", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX12", alias="slit_bottom", label="bottom", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX13",alias="slit_top",label="top",mtype="smaract_motor",), + ], + ) + + def build_xeye_group(self, toolbox): + qutilities.add_item_to_toolbox( + toolbox, + "X-Ray Eye", + widget_list=[ + self.get_tweaker("SARES30-ESBMX14", alias="xeye_x", label="X", mtype="smaract_motor"), + self.get_tweaker("SARES30-ESBMX15", alias="xeye_y", label="Y", mtype="smaract_motor"), + ], + ) + + def build_posttube_group(self, toolbox): + widgets = [ + self.get_tweaker("SARES30-ESBMX4", alias="tube_usx", label="upstream X", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX6", alias="tube_usy", label="upstream Y", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX5", alias="tube_dsx", label="downstream X", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX7", alias="tube_dsy", label="downstream Y", mtype="smaract_motor",), + self.get_tweaker("SARES30-ESBMX8", alias="tube_z", label="tube Z", mtype="smaract_motor"), + ] + c = QWidget() + c.setLayout(QGridLayout()) + but = QPushButton("post tube out") + but.clicked.connect(lambda m, dir="out": self.move_post_tube(dir)) + c.layout().addWidget(but, 0, 0) + but = QPushButton("post tube in") + but.clicked.connect(lambda m, dir="in": self.move_post_tube(dir)) + c.layout().addWidget(but, 0, 1) + widgets.append(c) + + label = "Post Sample Tube" + block = QWidget() + block.setAccessibleName(label) + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + for w in widgets: + block.layout().addWidget(w) + + block.layout().addWidget(self.build_post_tube_tandem_tweaker()) + block.layout().addStretch() + toolbox.addItem(block, label) + + def build_post_tube_tandem_tweaker(self): + block = QWidget() + block.setAccessibleName("post tube tamden") + block.setContentsMargins(0, 0, 0, 0) + block.setLayout(QVBoxLayout()) + c = QWidget() + glay = QGridLayout() + c.setLayout(glay) + + b = QWidget() + blay = QHBoxLayout() + b.setLayout(blay) + blay.addWidget(QLabel("Tweak (mm)")) + self._post_tandem_tweak_val = QLineEdit("1.0") + blay.addWidget(self._post_tandem_tweak_val) + glay.addWidget(b, 0, 0, 1, 2) + + icon = qtawesome.icon("material.arrow_back") + but = QPushButton(icon, "") + but.clicked.connect(lambda m, dir="x-pos": self.move_post_tube(dir)) + glay.addWidget(but, 1, 0) + + # c.setLayout(glay) + icon = qtawesome.icon("material.arrow_forward") + but = QPushButton(icon, "") + but.clicked.connect(lambda m, dir="x-neg": self.move_post_tube(dir)) + glay.addWidget(but, 1, 1) + + # c.setLayout(glay) + icon = qtawesome.icon("material.arrow_downward") + but = QPushButton(icon, "") + but.clicked.connect(lambda m, dir="down": self.move_post_tube(dir)) + glay.addWidget(but, 2, 0) + + # c.setLayout(glay) + icon = qtawesome.icon("material.arrow_upward") + but = QPushButton(icon, "") + but.clicked.connect(lambda m, dir="up": self.move_post_tube(dir)) + glay.addWidget(but, 2, 1) + + block.layout().addWidget(c) + return block + + def assert_post_tube_position(self, pos): + x_up = settings.value("post_sample_tube/x_up", 1e10, type=float) + y_up = settings.value("post_sample_tube/y_up", 1e10, type=float) + x_down = settings.value("post_sample_tube/x_down", 1e10, type=float) + y_down = settings.value("post_sample_tube/y_down", 1e10, type=float) + dx = settings.value("post_sample_tube/dx", 1e10, type=float) + dy = settings.value("post_sample_tube/dy", 1e10, type=float) + tz_in = settings.value("post_sample_tube/z_in", 1e10, type=float) + tz_out = settings.value("post_sample_tube/z_out", 1e10, type=float) + + if (x_up + y_up + x_down + y_down + dx + dy + tz_in + tz_out) > 1e9: + raise Exception("miscofingured positions for post-sample tube") + + usy = self.tweakers["tube_usy"] + dsy = self.tweakers["tube_dsy"] + usx = self.tweakers["tube_usx"] + dsx = self.tweakers["tube_dsx"] + tbz = self.tweakers["tube_z"] + + if pos == "in": + yu = y_up + xu = x_up + yd = y_down + xd = x_down + z = tz_in + elif pos == "out": + yu = y_up + dy + xu = x_up + dx + yd = y_down + dy + xd = x_down + dx + z = tz_out + + app_utils.assert_tweaker_positions([ + (usy, yu, 0.1), + (dsy, yd, 0.1), + (usx, xu, 0.1), + (dsx, xd, 0.1), + (tbz, z, 0.1),],timeout=2.0, + ) + + def move_post_tube(self, dir): + try: + tandem_twv = float(self._post_tandem_tweak_val.text()) + except: + tandem_twv = 1.0 + + x_up = settings.value("post_sample_tube/x_up") + y_up = settings.value("post_sample_tube/y_up") + x_down = settings.value("post_sample_tube/x_down") + y_down = settings.value("post_sample_tube/y_down") + dx = settings.value("post_sample_tube/dx") + dy = settings.value("post_sample_tube/dy") + tz_in = settings.value("post_sample_tube/z_in") + tz_out = settings.value("post_sample_tube/z_out") + if x_up is None: + msg = "SwissMX *POST-SAMPLE-TUBE* configuration is incomplete!!!" + _log.warning(msg) + QMessageBox.warning(self, "post tube not configured", msg) + return + x_up = float(x_up) + y_up = float(y_up) + x_down = float(x_down) + y_down = float(y_down) + dx = float(dx) + dy = float(dy) + tz_in = float(tz_in) + tz_out = float(tz_out) + + usy = self.tweakers["tube_usy"] + dsy = self.tweakers["tube_dsy"] + + usx = self.tweakers["tube_usx"] + dsx = self.tweakers["tube_dsx"] + tube_z = self.tweakers["tube_z"] + + if dir == "in": + _log.info("move post sample tube in") + usy.move_motor_to_position(y_up) + dsy.move_motor_to_position(y_down) + usx.move_motor_to_position(x_up) + dsx.move_motor_to_position(x_down) + try: + app_utils.assert_tweaker_positions([ + (usy, y_up, 0.1), + (dsy, y_down, 0.1), + (usx, x_up, 0.1), + (dsx, x_down, 0.1), ],timeout=10.0, + ) + except app_utils.PositionsNotReached as e: + _log.warning("failed to move post sample tube {}".format(dir)) + _log.warning(e) + QMessageBox.warning(self, "failed to move post sample tube XY {in}", "failed to move post sample tube XY {in}",) + raise + tube_z.move_motor_to_position(tz_in, wait=True) + try: + app_utils.assert_tweaker_positions([(tube_z, tz_in, 0.1)]) + except app_utils.PositionsNotReached as e: + _log.warning("failed to move post sample tube Z {in}") + _log.warning(e) + QMessageBox.warning(self, "failed to move post sample tube Z {in}", "failed to move post sample tube Z {in}",) + raise + + elif dir == "out": + _log.info("move post sample tube out") + tube_z.move_motor_to_position(tz_out, wait=True) + try: + app_utils.assert_tweaker_positions([(tube_z, tz_out, 0.1)]) + except app_utils.PositionsNotReached as e: + _log.warning("failed to move post sample tube {out}") + _log.warning(e) + QMessageBox.warning(self,"failed to move post sample tube Z {out}","failed to move post sample tube Z {out}",) + raise + usy.move_motor_to_position(y_up + dy) + dsy.move_motor_to_position(y_down + dy) + usx.move_motor_to_position(x_up + dx) + dsx.move_motor_to_position(x_down + dx) + try: + app_utils.assert_tweaker_positions([ + (usy, y_up + dy, 0.1), + (dsy, y_down + dy, 0.1), + (usx, x_up + dx, 0.1), + (dsx, x_down + dx, 0.1), ], timeout=10.0, + ) + except app_utils.PositionsNotReached as e: + _log.warning("failed to move post sample tube {}".format(dir)) + _log.warning(e) + QMessageBox.warning(self,"failed to move post sample tube XY {out}","failed to move post sample tube XY {out}",) + raise + elif dir == "x-pos": + _log.info("tamdem move post sample tube X-pos by {} mm".format(tandem_twv)) + usx.move_relative(tandem_twv) + dsx.move_relative(tandem_twv) + elif dir == "x-neg": + _log.info("tamdem move post sample tube X-neg {} mm".format(tandem_twv)) + usx.move_relative(-tandem_twv) + dsx.move_relative(-tandem_twv) + elif dir == "up": + _log.info("tamdem move post sample tube UP {} mm".format(tandem_twv)) + usy.move_relative(tandem_twv) + dsy.move_relative(tandem_twv) + elif dir == "down": + _log.info("tamdem move post sample tube DOWN {} mm".format(tandem_twv)) + usy.move_relative(-tandem_twv) + dsy.move_relative(-tandem_twv) + + def get_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None, **kwargs): + if mtype == "epics_motor": + m = MotorTweak() + else: + m = SmaractMotorTweak() + #m.connect_motor(pv, label, **kwargs) #ZAC: orig. code + self.tweakers[alias] = m + return m + + def add_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None): + if layout is None: + layout = self._tweak_container.layout() + if mtype == "epics_motor": + m = MotorTweak() + else: + m = SmaractMotorTweak() + layout.addWidget(m) + #m.connect_motor(pv, label)#ZAC: orig. code + self.tweakers[alias] = m + + def done_sliding(self): + print("done sliding at {}".format(self.slider_fast_x.value())) + + @pyqtSlot() + def saveSampleCameraScreenshot(self): + outf = self.get_screenshot_filename() + _log.info("saving original clean screenshot: {}".format(outf)) + try: + sample_camera.saveimage(outf) + except Exception as e: + _log.warning(e) + QMessageBox.warning(self, "Screenshot: failed to save image", "Failed to save screenshot!") + + @pyqtSlot() + def saveSampleCameraScreenshotView(self): + outf = self.get_screenshot_filename() + _log.info("saving view screenshot: {}".format(outf)) + + exporter = pg.exporters.ImageExporter(self.vb) + + # set export parameters if needed + exporter.parameters()["width"] = 2000 # (note this also affects height parameter) + + # save to file + try: + exporter.export(outf) + except Exception as e: + _log.warning(e) + QMessageBox.warning(self, "Screenshot: failed to save viewer image", "Failed to save screenshot of viewer!",) + + def get_screenshot_filename(self): + global folders + _log.info("taking screenhot") + prefix = folders.prefix + folder = folders.res_folder + base = time.strftime("{}_%Y%m%d_%H%M%S.png".format(prefix)) + if not os._exists(folder): + try: + os.makedirs(folder, 0o750, exist_ok=True) + except: + msg = "Failed to create folder: {}".format(folder) + _log.warning(msg) + QMessageBox.warning(self, "Screenshot: failed to create folder", "Failed to create output folder for screenshot!\n\n\tScreenshot not taken!",) + raise + outf = os.path.join(folder, base) + return outf + + def daq_grid_add_grid(self, gx=None, gy=None): + grid_index = len(self._grids) + if gx in (False, None): + gx, gy = self.getFastX(), self.getFastY() + xstep = self._sb_grid_x_step.value() + ystep = self._sb_grid_y_step.value() + xoffset = self._sb_grid_x_offset.value() + yoffset = self._sb_grid_y_offset.value() + grid = Grid( x_step=xstep, y_step=ystep, x_offset=xoffset, y_offset=yoffset, gonio_xy=(gx, gy), grid_index=grid_index, parent=self,) + self._grids.append(grid) + grid.calculate_gonio_xy() + grid.sigRemoveRequested.connect(lambda g=grid: self.remove_grid(g)) + self.vb.addItem(grid) + + def daq_grid_remove_all(self): + for grid in self._grids: + grid.sigRemoveRequested.emit(grid) # ugly hack + self._grids = [] + + def grids_pause_stage_tracking(self): + for grid in self._grids: + grid.disable_stage_tracking() + + def grids_start_stage_tracking(self): + for grid in self._grids: + grid.enable_stage_tracking() + + def remove_grid(self, grid): + self.vb.removeItem(grid) + self._grids.remove(grid) + + + def daq_grid_update(self, grid_index): + try: + grid = self._grids[grid_index] + except: + print("grid index not yet there") + return + points = grid.get_grid_targets() + num_points = len(points) + etime = float(settings.value("exposureTime")) + doc = f"grid_{grid_index} = [" + for n, pos in enumerate(points): + x, y = pos + doc += "[{:8.3f}, {:8.3f}],\n".format(x, y) + doc += "]" + self._grid_inspect_area.setPlainText(doc) + m = "Number of points: {}\nEstimated Time: {:.1f} minutes".format(num_points, num_points * etime / 60.) + self._label_grid_parameters.setText(m) + + def daq_embl_collect_points(self): + coords = self._embl_module.coords + points = [[x, y] for x, y, bx, bz, o in coords] + points = np.array(points) + method = "trajectory" + xp = (1000 * points).astype(int) # microns then round int + params = (xp[:, 0].tolist(), xp[:, 1].tolist()) + self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) + + def daq_prelocated_collect_points(self): + points = [] + data = self.prelocationModule.get_collection_targets() + for n, cc in enumerate(data): + is_fiducial, gx, gy, cx, cy, ox, oy = cc + points.append([gx, gy]) + points = np.array(points) + method = "trajectory" + xp = (1000 * points).astype(int) # microns then round int + params = (xp[:, 0].tolist(), xp[:, 1].tolist()) + self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) + + def daq_grid_collect_grid(self): + grid = self._grids[0] # FIXME one grid at a time only + points = np.array(grid.get_grid_targets()) + method = "grid" + params = grid._grid_dimensions + # params = ([grid._grid_dimensions[0]], [grid._grid_dimensions[1]]) # FIXME something wrong here< + self.daq_collect_points(points, visualizer_method=method, visualizer_params=params) + + def daq_grid_findxtals(self): + feature_size = self._sb_findxtals_feature_size.value() + image = sample_camera.get_image() + findObj(-image, objSize=feature_size, viz=1) + + def check_jungfrau_save(self) -> bool: + if jungfrau_detector.is_running_detector(): + saveRaw = jungfrau_detector.is_saving_data() + + if not saveRaw: + box = QMessageBox() + box.setText("Jungfrau save data disabled!") + box.setInformativeText("Jungfrau save data is disabled!") + box.setIcon(QMessageBox.Warning) + box.setDetailedText("Choose to abort, enable and continue, or continue without saving raw data") + btContinue = box.addButton("Continue", QMessageBox.YesRole) + btAbort = box.addButton("OMG! Abort", QMessageBox.NoRole) + btEnable = box.addButton("Enable save and continue", QMessageBox.YesRole) + box.exec_() + ans = box.clickedButton() + if ans == btEnable: + jungfrau_detector.set_save_raw(True) + return True + elif ans == btAbort: + _log.info("not doing helical scan") + return False + return True + return True + + def daq_collect_points(self, points, visualizer_method, visualizer_params): + task = self.active_task() + XDIR = -1 + + folders.make_if_needed() + + if ( option(ACTIVATE_PULSE_PICKER) and not jungfrau_detector.is_running_detector()): + if QMessageBox.No == QMessageBox.question(self, "X-rays but no Jungfrau", + "X-rays will be used bu the Jungfrau will not run.\n\n\tContinue?",): + _log.warning("user forgot to turn on the jungfrau") + return + + if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): + self.escape_goToDataCollection() + + ntrigger = len(points) + points *= 1000 # delta tau uses micrometers + points[:, 0] *= XDIR # fast X axis is reversed + + etime = settings.value("exposureTime", type=float) + vscale = settings.value(DELTATAU_VELOCITY_SCALE, 1.0, type=float) + sort_points = option(DELTATAU_SORT_POINTS) + + # sync_mode : default=2 + # 0 : no sync at all + # 1 : synchronize start + # 2 : synchronize start and adapt motion speed + # this function generates the code blocks: + # self.sync_wait and self.sync_run + # sync_wait can be put in the program to force a timing sync + # sync_run are the commands to run the whole program + # sync_flag if not using jungfrau =1 otherwise =0 + # D.O. shapepath.meta.update(sync_mode=2, sync_flag=1) + shapepath.meta.update(sync_mode=0, sync_flag=0) + maxacq_points = 116508 + samp_time = 0.0002 # s + overhead_time = 0.1 + acq_per = int(np.ceil((etime * ntrigger + overhead_time) / (maxacq_points * samp_time))) + _log.info(f"gather acquisotion period = {acq_per}") + _log.info(f"velocity scale {vscale}") + shapepath.setup_gather(acq_per=acq_per) + shapepath.setup_sync(verbose=True) + shapepath.setup_coord_trf() + shapepath.points = np.copy(points) + + if TASK_GRID == task: + width, height = visualizer_params + _log.debug(f"grid: {width} x {height}") + details_1 = [width] + details_2 = [height] + shapepath.sort_points(xy=False, grp_sz=height) + elif task in (TASK_PRELOCATED, TASK_EMBL): + if sort_points: + shapepath.sort_points() + self.daq_method_prelocated_remove_markers() + details_1, details_2 = visualizer_params + + shapepath.setup_motion( + mode=3, # 1 = bad pvt 3 = pft (pvt via inverse fourier transform) + pt2pt_time=etime * 1000., + fnPrg=folders.get_prefixed_file("_program.prg"), + scale=vscale, # velocity at target position scaling: 1=optimal speed, 0=zero speed + dwell=10, # milli-seconds wait + ) + shapepath.run() + + self.qprogress = QProgressDialog(self) + self.qprogress.setRange(0, 0) + self.qprogress.setLabelText("Acquiring GRID") + self.qprogress.setCancelButtonText("Abort Acquisition") + self.qprogress.canceled.connect(self.complete_daq) + self.qprogress.setWindowModality(Qt.WindowModal) + self.qprogress.setAutoClose(True) + self.qprogress.show() + + sequencer_steps = [lambda: self.grids_pause_stage_tracking()] + + if jungfrau_detector.is_running_detector(): + if not self.check_jungfrau_save(): + # user aborted run from save data dialog + return + + n_frames = ntrigger + uid = settings.value(EXPERIMENT_UID, type=int) + backend_extras = self.jungfrau.get_parameters() + backend_extras.update( + { + "swissmx_trajectory_method": visualizer_method, + "swissmx_trajectory_details_1": details_1, + "swissmx_trajectory_details_2": details_2, + } + ) + jungfrau_detector.set_number_of_frames(n_frames) + jungfrau_detector.set_data_owner_uid(uid) + sequencer_steps.extend( + [ + lambda: jungfrau_detector.configure( + n_frames=n_frames, + outfile=folders.prefix, + outdir=folders.raw_folder, + uid=uid, + backend_extras=backend_extras, + ), + lambda: jungfrau_detector.arm(), + ] + ) + + sequencer_steps.append(lambda: shapepath.wait_armed()) + if option(ACTIVATE_PULSE_PICKER): + sequencer_steps.append(lambda: pulsePicker.open()) + + # if settings.value("scanning/trigger_microscope_camera", type=bool): + # sample_camera.switch_to_trigger(True) + + sequencer_steps.append(lambda: shapepath.trigger(wait=0.5)) + + def shapepath_progress(): + while True: + p = shapepath.progress() + if p < 0: + break + time.sleep(0.1) + self.qprogress.setLabelText(f"Acquiring GRID {p:.0f} / {ntrigger}") + _log.warning(f"motion complete!") + # sample_camera.stop_camera() + # sample_camera.switch_to_trigger(False) + # sample_camera.save_buffer_series(folders.prefix) + + sequencer_steps.append(shapepath_progress) + + if option(ACTIVATE_PULSE_PICKER): + sequencer_steps.append(lambda: pulsePicker.close()) + + sequencer_steps.append(lambda: jungfrau_detector.wait_finished()) + + sequencer_steps.append(lambda: self.grids_start_stage_tracking()) + + self.sequencer = Sequencer(steps=sequencer_steps) + self._thread = QThread() + self._thread.setObjectName("acquisition_thread") + self.sequencer.moveToThread(self._thread) + self.sequencer.finished.connect(self.daq_collect_points_wrapup) + self._thread.started.connect(self.sequencer.run_sequence) + self._thread.start() + + def run_steps(self, steps, title, at_end=None, cancelable=False): + dlg = QProgressDialog(self) + dlg.setWindowTitle(title) + dlg.setWindowModality(Qt.WindowModal) + dlg.setMinimumDuration(0) + if not cancelable: + dlg.setCancelButton(None) + dlg.setRange(0, 0) + dlg.setLabelText(f"{title}
") + dlg.setAutoClose(True) + dlg.show() + dlg.setValue(random.randint(1, 20)) + class Runner(QObject): + finito = pyqtSignal() + timeoutExpired = pyqtSignal() + errorHappened = pyqtSignal(str) + result = pyqtSignal(str) + + def __init__(self, step_to_run): + super().__init__() + self.step = step_to_run + self.exception = None + self.done = False + + def run(self): try: - self._eigerwaitthread._is_aborted = True - except: - pass - _log.warning("aborting grid scan") - self.abort_measurement() - delta_tau.abort() - jungfrau_detector.disarm() - self.re_connect_collect_button() - - def execute_collection(self): - task = self.active_task() - self._is_aborted = False - method = self._tabs_daq_methods.currentWidget().accessibleName() - - if task == TASK_POINT_SHOOT: - QMessageBox.information("not implemented", "not implemented") - self.re_connect_collect_button( - callback=self.collect_abort_grid, - accessibleName="grid_abort", - label="Abort", - ) - - elif task == TASK_GRID: - self.re_connect_collect_button( - callback=self.collect_abort_grid, - accessibleName="grid_abort", - label="Abort Grid Scan", - ) - self._inspect = self._grid_inspect_area - self._inspect.setPlainText("") - self.daq_grid_collect_grid() - elif task == TASK_PRELOCATED: - self._inspect = self._preloc_inspect_area - self._inspect.setPlainText("") - self.daq_prelocated_collect_points() - elif task == TASK_HELICAL: - self.daq_helical_collect() - elif task == TASK_EMBL: - self.daq_embl_collect_points() - - def create_escape_toolbar(self): - cont = self._rightmost - w = QGroupBox("Escape") - layout = QHBoxLayout() - w.setLayout(layout) - but = QPushButton("Exchange\nSample") - but.setAccessibleName("escape_button_se") - but.setObjectName("action_SampleExchange") - but.clicked.connect(self.escape_goToSampleExchange) - layout.addWidget(but) - but = QPushButton("Alignment") - but.setAccessibleName("escape_button_sa") - but.clicked.connect(self.escape_goToSampleAlignment) - layout.addWidget(but) - but = QPushButton("Collection") - but.setAccessibleName("escape_button_dc") - but.clicked.connect(self.escape_goToDataCollection) - layout.addWidget(but) - cont.layout().addWidget(w) - - def init_actions(self): - self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S), self) - self.shortcut.activated.connect(self.saveSampleCameraScreenshot) - - self.shortcut = QShortcut(QKeySequence(Qt.Key_F2), self) - self.shortcut.activated.connect(self.saveSampleCameraScreenshot) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_S), self) - self.shortcut.activated.connect(self.saveSampleCameraScreenshotView) - - 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) - self.shortcut.activated.connect(lambda key=k: self.saveBookmark(key)) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + qkey), self) - self.shortcut.activated.connect(lambda key=k: self.gotoBookmark(key)) - - self._button_collect.clicked.connect(self.execute_collection) - - # Toolbar buttons - icon_size = QSize(50, 50) - - icon = qtawesome.icon("material.photo_camera") - action = QAction(icon, "Save View", self) - action.setToolTip("(Ctrl+S) Take a screenshot of the currently visible sample image, including markers. Saves in current folder.") - action.triggered.connect(self.saveSampleCameraScreenshotView) - self.toolBar.addAction(action) - - action = QAction(icon, "Save Original", self) - action.setToolTip("(Ctrl+Shift+S) Take a screenshot of the sample image, without markers. Saves in current folder.") - action.triggered.connect(self.saveSampleCameraScreenshot) - self.toolBar.addAction(action) - - if os.getenv("DEVELOPMENT_VERSION"): - icon = qtawesome.icon("material.style") - action = QAction(icon, "Reload Stylesheet", self) - action.triggered.connect(self.load_stylesheet) - self.toolBar.addAction(action) - - # icon = qtawesome.icon("material.shutter_speed") - action = QAction("Use Pulse Picker", self) - action.setCheckable(True) - action.setChecked(option(ACTIVATE_PULSE_PICKER)) - action.triggered.connect(lambda: toggle_option(ACTIVATE_PULSE_PICKER)) - self.toolBar.addAction(action) - - icon = qtawesome.icon("material.timeline") - action = QAction(icon, "Add Line", self) - action.triggered.connect(self.roi_add_line) - self.toolBar.addAction(action) - - icon = qtawesome.icon("material.tab_unselected") - action = QAction(icon, "Add Rect", self) - action.triggered.connect(self.roi_add_rect) - self.toolBar.addAction(action) - - icon = qtawesome.icon("material.play_for_work") - action = QAction(icon, "TELL Mount", self) - action.setToolTip("BookMark(0) => Move stages CZ, CX, FX, FY, and Omega to Tell sample changer mount position") - action.triggered.connect(self.escape_goToTellMountPosition) - self.toolBar.addAction(action) - - self.toolBar.addWidget(qutilities.horiz_spacer()) - - icon = qtawesome.icon("material.sync") - action = QAction(icon, "Sample\nExchange", self) - action.setToolTip("Move devices so a sample can be exchanged.") - action.setObjectName("action_SampleExchange") - action.triggered.connect(self.escape_goToSampleExchange) - self.toolBar.addAction(action) - self.toolBar.widgetForAction(action).setAccessibleName("action_SampleExchange") - - icon = qtawesome.icon("material.my_location") - action = QAction(icon, "Sample\nAlignment", self) - action.setToolTip("Move devices so a sample can be aligned.") - action.triggered.connect(self.escape_goToSampleAlignment) - self.toolBar.addAction(action) - self.toolBar.widgetForAction(action).setAccessibleName("action_SampleAlignment") - - icon = qtawesome.icon("material.fingerprint") - action = QAction(icon, "Data\nCollection", self) - action.setToolTip("Move devices so a sample can be collected.") - action.triggered.connect(self.escape_goToDataCollection) - self.toolBar.addAction(action) - self.toolBar.widgetForAction(action).setAccessibleName("action_DataCollection") - - self.actionQuit.triggered.connect(self.really_quit) - self.actionPreferences.triggered.connect(self.openPreferencesDialog) - self.actionHome_Fast_Stages.triggered.connect(self.home_deltatau_faststages) - self.actionUser_Storage.triggered.connect(self.update_user_and_storage) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_L), self) - self.shortcut.activated.connect(self.roi_add_line) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_R), self) - self.shortcut.activated.connect(self.roi_add_rect) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_M), self) - self.shortcut.activated.connect(self.toggle_mouse_tracking) - - self.shortcut = QShortcut(QKeySequence(Qt.Key_F11), self) - self.shortcut.activated.connect(self.toggle_maximized) - - self.shortcut = QShortcut(QKeySequence(Qt.Key_F12), self) - self.shortcut.activated.connect(self.show_window_configuration) - - self.shortcut = QShortcut(QKeySequence(Qt.Key_F5), self) - self.shortcut.activated.connect(self.generic_dialog) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_B), self) - self.shortcut.activated.connect(lambda: self._beammark.toggle_handle()) - - self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_P), self) - self.shortcut.activated.connect(self.camera_pause_toggle) - - # adding a menu entry to one of the menus - action = QAction("Beam Marker Size", self) - action.setToolTip("Update the beam marker size on GUI; *not* the actual beam size!") - action.setStatusTip("Update the beam marker size on GUI; *not* the actual beam size!") - action.triggered.connect(self.set_beam_size_marker_dialog) - self.menuAdvanced.addAction(action) - - action = QAction("Backlight Reference Positions", self) - action.setToolTip("Update the reference positions for the backlight") - action.setStatusTip("Update the reference positions for the backlight") - action.triggered.connect(self.set_backlight_positions_dialog) - self.menuAdvanced.addAction(action) - - action = QAction("Collimator Reference Positions", self) - action.setToolTip("Update the reference positions for the collimator") - action.setStatusTip("Update the reference positions for the collimator") - action.triggered.connect(self.set_collimator_reference_positions) - self.menuAdvanced.addAction(action) - - action = QAction("Cryojet Reference Positions", self) - action.setToolTip("Update the reference positions for the cryojet nozzle position") - action.setStatusTip("Update the reference positions for the cryojet nozzle position") - action.triggered.connect(self.set_cryojet_positions_dialog) - self.menuAdvanced.addAction(action) - - action = QAction("Post sample tube Reference Positions", self) - action.setToolTip("Update the reference positions for the post tube") - action.setStatusTip("Update the reference positions for the post tube") - action.triggered.connect(self.set_posttube_references_dialog) - self.menuAdvanced.addAction(action) - - action = QAction("Delta Tau Parameters", self) - action.setToolTip("Parameters affecting the Delta Tau") - action.setStatusTip("Parameters affecting the Delta Tau") - action.triggered.connect(self.set_deltatau_parameters) - self.menuAdvanced.addAction(action) - - action = QAction("Tell Mount Positions", self) - action.setToolTip("Mount positions for TELL sample changer") - action.setStatusTip("Parameters affecting the Delta Tau") - action.triggered.connect(self.set_tell_mount_positions) - self.menuAdvanced.addAction(action) - - def daq_method_prelocated_remove_markers(self): - try: - for m in self._marker_rois: - m.disconnect_signals() - self.vb.removeItem(m) + self.step() except Exception as e: - _log.warning("maybe failed removing markers: {}".format(e)) - self._marker_rois = [] + _log.debug(" +> step exception") + self.exception = str(e) + self.errorHappened.emit(str(e)) + self.finito.emit() - def pause_all_markers(self): - for m in self._marker_rois: - m.disconnect_signals() - def resume_all_markers(self): - for m in self._marker_rois: - m.reconnect_signals() + for n, step in enumerate(steps): + _log.info(f"running step {step.title}") + dlg.setLabelText(f"{title}
{step.title}") + dlg.setValue(n) + thread = QThread() + runner = Runner(step) + runner.moveToThread(thread) + thread.started.connect(runner.run) + runner.finito.connect(thread.quit) + thread.start() + while thread.isRunning(): + dlg.setValue(random.randint(1, 20)) + time.sleep(0.01) + if dlg.wasCanceled(): + # FIXME ensure we abort the running thread + break - def daq_method_prelocated_update_markers(self): - self.daq_method_prelocated_remove_markers() - data = self.prelocationModule.get_data() - add_xtals = self.prelocationModule._xtals_transformed - draw_xtals = self.prelocationModule.set_draw_crystal_marks - vb = self.vb - self._marker_rois = [] - ppm = self.getPpm() - for n, cc in enumerate(data): - is_fiducial, gx, gy, cx, cy, ox, oy = cc - if not is_fiducial: - if not (add_xtals and draw_xtals): - continue - _log.debug(f"adding {'fiducial' if is_fiducial else 'xtal'} mark #{n}: {is_fiducial} {gx:.3f}, {gy:.3f}, {cx:.1f}, {cy:.1f}") - m = CrystalCircle( - gonio_xy=(gx, gy), - parent=self, - model_row_index=n, - is_fiducial=is_fiducial, - ppm=ppm, - ) - # m.sigRegionChangeFinished.connect(lambda roi=m: self.daq_method_prelocated_update_model(roi)) - self._marker_rois.append(m) - vb.addItem(m) - for c in self._marker_rois: - c.reconnect_signals() - c.follow_stage() + if dlg.wasCanceled(): + break + if runner.exception is not None: + QMessageBox.critical(self, step.title, str(runner.exception)) + break - def daq_method_prelocated_set_fiducial(self, camx, camy, gx, gy): - _log.debug(f"camx, camy: {camx}, {camy}, fx, fy: {gx, gy}") - self.prelocationModule.set_fiducial_coords(camx, camy, gx, gy) + if dlg.wasCanceled(): + _log.error(f"sequence {title} was cancelled by user") + raise AcquisitionAbortedException(f"sequence {title} was cancelled by user") - def daq_method_prelocated_append_data(self, x, y, gx, gy): - _log.debug("appending to model: {} {}".format((x, y), (gx, gy))) - self.prelocationModule.append_data((x, y, gx, gy)) - self.daq_method_prelocated_update_markers() + if at_end is not None: + at_end() + dlg.reset() - def daq_method_prelocated_update_model(self, roi): - row = roi.get_model_row() - pos = roi.pos() - self.prelocationModule.set_data_camera(row, pos) - _log.debug("updating row {} => {}".format(row, pos)) + def daq_collect_points_wrapup(self): + self.qprogress.reset() + if self._thread.isRunning(): + self._thread.quit() + shapepath.gather_upload(os.path.join(folders.res_folder, folders.prefix + ".npz")) - def daq_method_prelocated_add_reference(self): - self._references.append(pg.CircleROI()) + if option(DELTATAU_SHOW_PLOTS): + dp = DebugPlot(shapepath) + dp.plot_gather(mode=1) + pyplot.show() - def build_daq_methods_prelocated_tab(self): - tab = self._tab_daqmethod_prelocated - self.prelocationModule = PrelocatedCoordinatesModel.PrelocatedCoordinates(parent=self) - tab.layout().addWidget(self.prelocationModule) - self.prelocationModule.prefixSelected.connect(lambda prefix: self._le_prefix.setText(prefix)) - self.prelocationModule.dataFileLoaded.connect(self.daq_method_prelocated_update_markers) - self.prelocationModule.prelocatedDataUpdated.connect(self.daq_method_prelocated_update_markers) - self.prelocationModule.markersDeleted.connect(self.daq_method_prelocated_remove_markers) - self.fiducialPositionSelected.connect(self.daq_method_prelocated_set_fiducial) - self.appendPrelocatedPosition.connect(self.daq_method_prelocated_append_data) - self.prelocationModule.moveFastStageRequest.connect(self.move_fast_stage) - self._preloc_inspect_area = QPlainTextEdit() - tab.layout().addWidget(self._preloc_inspect_area) + if TASK_PRELOCATED == self.active_task(): + self.daq_method_prelocated_update_markers() - def move_fast_stage(self, x, y): - _log.info(f"received request to move gonio to x, y = {x:.3f}, {y:.3f} mm") - fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers() - fx_motor.move_motor_to_position(x) - fy_motor.move_motor_to_position(y) + if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): + self.escape_goToSampleAlignment() - def set_beam_size_marker_dialog(self): - w, h = settings.value(BEAM_SIZE) - d = GenericDialog( - title="Beamsize", - message="Enter the size of the beam in microns", - inputs={ - "width": ( - "Width", - int(w), - Spinner(int(w), min=1, max=200, suffix=" \u00B5m"), - ), - "height": ( - "Height", - int(h), - Spinner(int(h), min=1, max=200, suffix=" \u00B5m"), - ), - }, - ) - if d.exec(): - results = d.results - _log.info("Updating beamsize to {}".format(results)) - w, h = results["width"], results["height"] - _log.debug("types {}".format(type(w))) - settings.setValue(BEAM_SIZE, (w, h)) - self._beammark.set_beam_size((w, h)) - settings.sync() + sequence = {"delta tau program": shapepath.prg, "points": shapepath.points.tolist(), "timestamp": tdstamp(),} - def set_posttube_references_dialog(self): - x_up = settings.value("post_sample_tube/x_up", 0.0) - y_up = settings.value("post_sample_tube/y_up", 0.0) - x_down = settings.value("post_sample_tube/x_down", 0.0) - y_down = settings.value("post_sample_tube/y_down", 0.0) - dx = settings.value("post_sample_tube/dx", 0.0) - dy = settings.value("post_sample_tube/dy", 0.0) - tz_in = settings.value("post_sample_tube/z_in", 0.0) - tz_out = settings.value("post_sample_tube/z_out", 0.0) + sfname = folders.get_prefixed_file("_ScanInfo.json") + try: + with open(sfname, "w") as sf: + _log.info("writing scan info into: {}".format(sfname)) + sf.write(json.dumps(sequence)) + except: + _log.warning(f"failed to write scan info to {sfname}") - d = GenericDialog( - title="Post Sample Tube Configuration", - message="Enter the relative displacements for X and Y to move the post sample tube either in or out.", - inputs={ - "post_sample_tube/x_up": ( - "Up X", - float(x_up), - Spinner(float(x_up), decimals=3, min=-45.0, max=15.0, suffix=" mm"), - ), - "post_sample_tube/y_up": ( - "Up Y", - float(y_up), - Spinner(float(y_up), decimals=3, min=-45.0, max=15.0, suffix=" mm"), - ), - "post_sample_tube/x_down": ( - "Down X", - float(x_down), - Spinner( - float(x_down), decimals=3, min=-45.0, max=15.0, suffix=" mm" - ), - ), - "post_sample_tube/y_down": ( - "Down Y", - float(y_down), - Spinner( - float(y_down), decimals=3, min=-45.0, max=15.0, suffix=" mm" - ), - ), - "post_sample_tube/dx": ( - "out delta X", - float(dx), - Spinner(float(dx), decimals=3, min=-32.0, max=32.0, suffix=" mm"), - ), - "post_sample_tube/dy": ( - "out delta Y", - float(dy), - Spinner(float(dy), decimals=3, min=-32.0, max=32.0, suffix=" mm"), - ), - "post_sample_tube/z_in": ( - "tube Z in position", - float(tz_in), - Spinner(float(tz_in), decimals=3, min=-8.0, max=1.0, suffix=" mm"), - ), - "post_sample_tube/z_out": ( - "tube Z OUT position", - float(tz_out), - Spinner(float(tz_out), decimals=3, min=-8.0, max=1.0, suffix=" mm"), - ), - }, - ) - if d.exec(): - results = d.results - _log.info("setting post-sample-tube displacements {}".format(results)) - for k, v in results.items(): - settings.setValue(k, v) - settings.sync() + self.re_connect_collect_button() + jungfrau_detector.abort() + self.increaseRunNumberRequest.emit() - def set_collimator_reference_positions(self): - x_out = float(settings.value("collimator/dx", 0.0)) - y_out = float(settings.value("collimator/dy", 0.0)) - x_in = float(settings.value("collimator/x_in", 0.0)) - y_in = float(settings.value("collimator/y_in", 0.0)) - d = GenericDialog( - title="Collimator configuration", - message="Enter reference positions for the collimator", - inputs={ - "collimator/dx": ( - "Collimator out deltaX", - x_out, - Spinner(x_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"), - ), - "collimator/dy": ( - "Collimator out deltaY", - y_out, - Spinner(y_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"), - ), - "collimator/x_in": ( - "Collimator in X", - x_in, - Spinner(x_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"), - ), - "collimator/y_in": ( - "Collimator in Y", - y_in, - Spinner(y_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"), - ), - }, - ) - if d.exec(): - results = d.results - _log.info("setting collimator reference positions {}".format(results)) - for k, v in results.items(): - settings.setValue(k, v) - settings.sync() + def daq_collect_update_inspect(self, msg): + te = self._inspect + m = te.toPlainText() + te.setPlainText(m + msg + "\n") + te.verticalScrollBar().setValue(te.verticalScrollBar().maximum()) - def set_backlight_positions_dialog(self): - p_in = int(settings.value("backlight/in")) - p_out = int(settings.value("backlight/out")) - p_diode = int(settings.value("backlight/diode")) - d = GenericDialog( - title="Back Light configuration", - message="Enter reference positions for the backlight", - inputs={ - "backlight/in": ( - "In position", - p_in, - Spinner(p_in, min=-30000, max=10), - ), - "backlight/out": ( - "Out position", - p_out, - Spinner(p_out, min=-1000, max=10), - ), - "backlight/diode": ( - "Diode position", - p_diode, - Spinner(p_diode, min=-40000, max=-20000), - ), - }, - ) - if d.exec(): - results = d.results - _log.info("setting back light reference positions {}".format(results)) - for k, v in results.items(): - settings.setValue(k, v) - settings.sync() + def daq_helical_collect(self): + """[ + [{ + 0: (0.0, 1.238401694734829, 3.527, 0.936, 0.001), + 120: (0.0, 1.3890393596274455, -1.957242824091597, -0.5639999999999998, 120.001), + 240: (0.0, 1.3866130349657206, -1.1642619618562273, 3.105, 240.001) + }, + { + 0: (0.0, 1.238401694734829, 3.527, 0.936, 0.001), + 120: (0.0, 1.3890393596274455, -1.957242824091597, -0.5639999999999998, 120.001), + 240: (0.0, 1.3866130349657206, -1.1642619618562273, 3.105, 240.001) + }] + ] + """ + _log.info("executing collection") + htab = self._helical_scan_table + num_h = htab.scanHorizontalCount() + num_v = htab.scanVerticalCount() - def set_cryojet_positions_dialog(self): - p_in = settings.value(CRYOJET_NOZZLE_IN, type=float) - p_out = settings.value(CRYOJET_NOZZLE_OUT, type=float) - motion_enabled = option(CRYOJET_MOTION_ENABLED) - d = GenericDialog( - title="Cryojet Nozzle Configuration", - message="Enter reference positions for the cryojet nozzle position", - inputs={ - CRYOJET_NOZZLE_IN: ("In position", p_in, Spinner(p_in, min=3, max=15)), - CRYOJET_NOZZLE_OUT: ("Out position",p_out,Spinner(p_out, min=-1000, max=10),), - CRYOJET_MOTION_ENABLED: ("Move Cryojet in Transitions",motion_enabled,Checkbox(motion_enabled, "move cryojet"),), - }, - ) - if d.exec(): - results = d.results - _log.info("setting cryojet reference positions {}".format(results)) - for k, v in results.items(): - settings.setValue(k, v) - settings.sync() + if ( settings.value(ACTIVATE_PULSE_PICKER) and not jungfrau_detector.is_running_detector()): + if QMessageBox.No == QMessageBox.question(self, "X-rays but no Jungfrau", + "X-rays will be used bu the Jungfrau will not run.\n\n\tContinue?",): + _log.warning("user forgot to turn on the jungfrau") + return - def set_deltatau_parameters(self): - a = settings.value(DELTATAU_VELOCITY_SCALE, 1, type=float) - b = option(DELTATAU_SORT_POINTS) - c = option(DELTATAU_OMEGACOS) - d = option(DELTATAU_SHOW_PLOTS) + if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): + self.escape_goToDataCollection() - d = GenericDialog( - title="Delta Tau Parameters", - message="These parameters affect the data collection.", - inputs={ - DELTATAU_VELOCITY_SCALE: ("Velocity Scale (1=optimal, 0=zero vel at target)", a, - Spinner(a, min=0, max=1, suffix=""),), - DELTATAU_SORT_POINTS: ("Sort pointshoot/prelocated coords", b, - Checkbox(b, "sort points"),), - DELTATAU_SHOW_PLOTS: ("show plots after collection", d, - Checkbox(d, "correct"),), - DELTATAU_OMEGACOS: ( "preloc. correct omega tilt", c, - Checkbox(c, "correct"),), - }, - ) - if d.exec(): - results = d.results - _log.info("setting delta tau parameters {}".format(results)) - for k, v in results.items(): - settings.setValue(k, v) - settings.sync() + folders.make_if_needed() - def set_tell_mount_positions(self): - AUTODRY_ENABLED = "tell/autodry_enabled" - AUTODRY_MAXMOUNTS = "tell/autodry_max_number_of_mounts" - AUTODRY_MAXTIME = "tell/autodry_max_time" + data = htab.get_data() + _log.debug(data) + start, end = data[0] + FX, FY, BX, BZ, O = range(5) + x = ( + (-1000 * start[0][BX], -1000 * start[120][BX], -1000 * start[240][BX]), + (-1000 * end[0][BX], -1000 * end[120][BX], -1000 * end[240][BX]), + ) + y = (1000 * start[0][FY], 1000 * end[0][FY]) + z = ( + (-1000 * start[0][BZ], -1000 * start[120][BZ], -1000 * start[240][BZ]), + (-1000 * end[0][BZ], -1000 * end[120][BZ], -1000 * end[240][BZ]), + ) - SECS_HOURS = 60 * 60 + if jungfrau_detector.is_running_detector(): + if not self.check_jungfrau_save(): + return + saveRaw = jungfrau_detector.is_saving_data() - enabled = option(AUTODRY_ENABLED) - maxtime = settings.value(AUTODRY_MAXTIME, type=float) / SECS_HOURS - maxmounts = settings.value(AUTODRY_MAXMOUNTS, type=int) + _log.debug(f"x = {x}") + _log.debug(f"y = {y}") + _log.debug(f"z = {z}") - d = GenericDialog( - title="TELL Settings", - message="These control some features of the TELL sample changer", - inputs={ - AUTODRY_ENABLED: ("Auto dry", enabled, - Checkbox(enabled, "enabled")), - AUTODRY_MAXMOUNTS: ("Max. num. mounts between dry",maxmounts, - Spinner(maxmounts, decimals=0, min=1, max=100, suffix=" mounts"),), - AUTODRY_MAXTIME: ("Max. time between dry",maxtime, - Spinner(maxtime, decimals=1, min=0.5, max=5, suffix=" hours"),), - }, - ) - if d.exec(): - results = d.results - _log.info("setting tell parameters {}".format(results)) - for k, v in results.items(): - if k == AUTODRY_MAXTIME: - v = v * SECS_HOURS - settings.setValue(k, v) - settings.sync() + oscillationAngle = settings.value("oscillationAngle", type=float) + exposureTime = 1000 * settings.value("exposureTime", type=float) + blastRadius = settings.value("blastRadius", type=float) + totalRange = num_v * num_h * oscillationAngle - def generic_dialog(self, title="test", message=""): - d = GenericDialog(title="Beamsize", message="some message") - if d.exec(): - results = d.results - print(results) + # sync_mode : default=2 + # 0 : no sync at all + # 1 : synchronize start + # 2 : synchronize start and adapt motion speed + # this function generates the code blocks: + # self.sync_wait and self.sync_run + # sync_wait can be put in the program to force a timing sync + # sync_run are the commands to run the whole program + # sync_flag if not using jungfrau =1 otherwise =0 + # D.O. helical.meta.update(sync_mode=2, sync_flag=1) + helical.meta.update(sync_mode=0, sync_flag=0) + helical.calcParam(x=x, y=y, z=z) + helical.setup_gather() + helical.setup_sync() + helical.setup_coord_trf() + mode = 1 + hRng = (-blastRadius * num_h, blastRadius * num_h) + w_start = 1000 * htab.startAngle() + wRng = (w_start, w_start + (totalRange * 1000)) + _log.info( + f"helical params mode={mode}, cnthor={num_h}, cntvert={num_v}, hrng={hRng}, wrng={wRng}" + ) + helical.setup_motion( + mode=mode, + cntHor=num_h, + cntVert=num_v, + hRng=hRng, + wRng=wRng, + pt2pt_time=exposureTime, + ) # hRng in micrometers + helical.run() + try: + with open(folders.get_prefixed_file("_helical_debug.cmd"), "w") as fd: + fd.write("calcParam(x={}, y={}, z={})".format(x, y, z)) + except: + pass - def toggle_maximized(self): - if self.isMaximized(): - self.showNormal() + self.qprogress = QProgressDialog(self) + self.qprogress.setRange(0, 0) + self.qprogress.setLabelText("Acquiring HELICAL") + self.qprogress.setCancelButtonText("Abort Acquisition") + self.qprogress.canceled.connect(self.complete_daq) + self.qprogress.setWindowModality(Qt.WindowModal) + self.qprogress.setAutoClose(True) + self.qprogress.show() + + sequencer_steps = [lambda: self.grids_pause_stage_tracking()] + + n_frames = num_h * num_v + if jungfrau_detector.is_running_detector(): + uid = settings.value(EXPERIMENT_UID, type=int) + backend_extras = self.jungfrau.get_parameters() + backend_extras.update( + { + "swissmx_trajectory_method": "grid", + "swissmx_trajectory_details_1": [-num_h], + "swissmx_trajectory_details_2": [num_v], + } + ) + + jungfrau_detector.set_number_of_frames(n_frames) + jungfrau_detector.set_data_owner_uid(uid) + sequencer_steps.extend( + [ + lambda: jungfrau_detector.configure( + n_frames=n_frames, + outfile=folders.prefix, + outdir=folders.raw_folder, + uid=uid, + backend_extras=backend_extras, + ), + lambda: jungfrau_detector.arm(), + ] + ) + + sequencer_steps.append(lambda: helical.wait_armed()) + if settings.value(ACTIVATE_PULSE_PICKER): + sequencer_steps.extend([lambda: pulsePicker.open(), lambda: pend_event(0.1)]) + + sequencer_steps.append(lambda: helical.trigger()) + + def motion_progress(): + while True: + p = helical.progress() + if p < 0: + break + time.sleep(0.1) + self.qprogress.setLabelText(f"Acquiring HELICAL {p:.0f} / {n_frames}") + _log.warning(f"helical motion complete!") + + sequencer_steps.append(motion_progress) + + if settings.value(ACTIVATE_PULSE_PICKER): + sequencer_steps.append(lambda: pulsePicker.close()) + + sequencer_steps.append(lambda: self.grids_start_stage_tracking()) + + self.sequencer = Sequencer(steps=sequencer_steps) + self._thread = QThread() + self._thread.setObjectName("acquisition_thread") + self.sequencer.moveToThread(self._thread) + self.sequencer.finished.connect(self.daq_helical_collect_wrapup) + self._thread.started.connect(self.sequencer.run_sequence) + self._thread.start() + + def daq_helical_collect_wrapup(self): + try: + self.qprogress.reset() + except: + pass + if self._thread.isRunning(): + self._thread.quit() + helical.gather_upload(os.path.join(folders.res_folder, folders.prefix + ".npz")) + + self.re_connect_collect_button() + jungfrau_detector.abort() + if option(ACTIVATE_PULSE_PICKER) or not option(SKIP_ESCAPE_TRANSITIONS_IF_SAFE): + self.escape_goToSampleAlignment() + self.increaseRunNumberRequest.emit() + + if option(DELTATAU_SHOW_PLOTS): + hsgui = HelicalScanGui(helical) + hsgui.interactive_anim() + + def complete_daq(self): + _log.info("daq completed: cleaning up") + try: + self.qprogress.reset() + except: + pass + try: + if self._thread.isRunning(): + self._thread.quit() + except: + pass + finally: + self.abort_measurement() + self.re_connect_collect_button() + + def build_daq_methods_grid_tab(self): + self._grids = [] + tab = self._tab_daq_method_grid + layout = tab.layout() # gridlayout + self._sb_grid_x_step.setValue(30.0) + self._sb_grid_y_step.setValue(30.0) + self._bt_add_grid.clicked.connect(self.daq_grid_add_grid) + self._bt_remove_all_grids.clicked.connect(self.daq_grid_remove_all) + self._find_targets_from_microscope_image.clicked.connect( + self.daq_grid_findxtals + ) + self.addGridRequest.connect(self.daq_grid_add_grid) + self.gridUpdated.connect(self.daq_grid_update) + + def re_connect_collect_button(self, callback=None, **kwargs): + _log.debug("re_connect_collect_button() {} => {}".format(callback, kwargs)) + return + # button = self._button_collect + # if callback is None: + # callback = self.execute_collection + # button.setAccessibleName("collect_button") + # kwargs["label"] = "Collect" + # + # try: + # button.disconnect() + # finally: + # button.clicked.connect(callback) + # + # if "accessibleName" in kwargs: + # button.setAccessibleName(kwargs["accessibleName"]) + # + # if "label" in kwargs: + # button.setText(kwargs["label"]) + # self.load_stylesheet() + + def collect_abort_grid(self): + self._is_aborted = True + try: + self._eigerwaitthread._is_aborted = True + except: + pass + _log.warning("aborting grid scan") + self.abort_measurement() + delta_tau.abort() + jungfrau_detector.disarm() + self.re_connect_collect_button() + + def execute_collection(self): + task = self.active_task() + self._is_aborted = False + method = self._tabs_daq_methods.currentWidget().accessibleName() + + if task == TASK_POINT_SHOOT: + QMessageBox.information("not implemented", "not implemented") + self.re_connect_collect_button( + callback=self.collect_abort_grid, + accessibleName="grid_abort", + label="Abort", + ) + + elif task == TASK_GRID: + self.re_connect_collect_button( + callback=self.collect_abort_grid, + accessibleName="grid_abort", + label="Abort Grid Scan", + ) + self._inspect = self._grid_inspect_area + self._inspect.setPlainText("") + self.daq_grid_collect_grid() + elif task == TASK_PRELOCATED: + self._inspect = self._preloc_inspect_area + self._inspect.setPlainText("") + self.daq_prelocated_collect_points() + elif task == TASK_HELICAL: + self.daq_helical_collect() + elif task == TASK_EMBL: + self.daq_embl_collect_points() + + def create_escape_toolbar(self): + cont = self._rightmost + w = QGroupBox("Escape") + layout = QHBoxLayout() + w.setLayout(layout) + but = QPushButton("Exchange\nSample") + but.setAccessibleName("escape_button_se") + but.setObjectName("action_SampleExchange") + but.clicked.connect(self.escape_goToSampleExchange) + layout.addWidget(but) + but = QPushButton("Alignment") + but.setAccessibleName("escape_button_sa") + but.clicked.connect(self.escape_goToSampleAlignment) + layout.addWidget(but) + but = QPushButton("Collection") + but.setAccessibleName("escape_button_dc") + but.clicked.connect(self.escape_goToDataCollection) + layout.addWidget(but) + cont.layout().addWidget(w) + + def init_actions(self): + self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S), self) + self.shortcut.activated.connect(self.saveSampleCameraScreenshot) + + self.shortcut = QShortcut(QKeySequence(Qt.Key_F2), self) + self.shortcut.activated.connect(self.saveSampleCameraScreenshot) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_S), self) + self.shortcut.activated.connect(self.saveSampleCameraScreenshotView) + + 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) + self.shortcut.activated.connect(lambda key=k: self.saveBookmark(key)) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + qkey), self) + self.shortcut.activated.connect(lambda key=k: self.gotoBookmark(key)) + + self._button_collect.clicked.connect(self.execute_collection) + + # Toolbar buttons + icon_size = QSize(50, 50) + + icon = qtawesome.icon("material.photo_camera") + action = QAction(icon, "Save View", self) + action.setToolTip("(Ctrl+S) Take a screenshot of the currently visible sample image, including markers. Saves in current folder.") + action.triggered.connect(self.saveSampleCameraScreenshotView) + self.toolBar.addAction(action) + + action = QAction(icon, "Save Original", self) + action.setToolTip("(Ctrl+Shift+S) Take a screenshot of the sample image, without markers. Saves in current folder.") + action.triggered.connect(self.saveSampleCameraScreenshot) + self.toolBar.addAction(action) + + if os.getenv("DEVELOPMENT_VERSION"): + icon = qtawesome.icon("material.style") + action = QAction(icon, "Reload Stylesheet", self) + action.triggered.connect(self.load_stylesheet) + self.toolBar.addAction(action) + + # icon = qtawesome.icon("material.shutter_speed") + action = QAction("Use Pulse Picker", self) + action.setCheckable(True) + action.setChecked(option(ACTIVATE_PULSE_PICKER)) + action.triggered.connect(lambda: toggle_option(ACTIVATE_PULSE_PICKER)) + self.toolBar.addAction(action) + + icon = qtawesome.icon("material.timeline") + action = QAction(icon, "Add Line", self) + action.triggered.connect(self.roi_add_line) + self.toolBar.addAction(action) + + icon = qtawesome.icon("material.tab_unselected") + action = QAction(icon, "Add Rect", self) + action.triggered.connect(self.roi_add_rect) + self.toolBar.addAction(action) + + icon = qtawesome.icon("material.play_for_work") + action = QAction(icon, "TELL Mount", self) + action.setToolTip("BookMark(0) => Move stages CZ, CX, FX, FY, and Omega to Tell sample changer mount position") + action.triggered.connect(self.escape_goToTellMountPosition) + self.toolBar.addAction(action) + + self.toolBar.addWidget(qutilities.horiz_spacer()) + + icon = qtawesome.icon("material.sync") + action = QAction(icon, "Sample\nExchange", self) + action.setToolTip("Move devices so a sample can be exchanged.") + action.setObjectName("action_SampleExchange") + action.triggered.connect(self.escape_goToSampleExchange) + self.toolBar.addAction(action) + self.toolBar.widgetForAction(action).setAccessibleName("action_SampleExchange") + + icon = qtawesome.icon("material.my_location") + action = QAction(icon, "Sample\nAlignment", self) + action.setToolTip("Move devices so a sample can be aligned.") + action.triggered.connect(self.escape_goToSampleAlignment) + self.toolBar.addAction(action) + self.toolBar.widgetForAction(action).setAccessibleName("action_SampleAlignment") + + icon = qtawesome.icon("material.fingerprint") + action = QAction(icon, "Data\nCollection", self) + action.setToolTip("Move devices so a sample can be collected.") + action.triggered.connect(self.escape_goToDataCollection) + self.toolBar.addAction(action) + self.toolBar.widgetForAction(action).setAccessibleName("action_DataCollection") + + self.actionQuit.triggered.connect(self.really_quit) + self.actionPreferences.triggered.connect(self.openPreferencesDialog) + self.actionHome_Fast_Stages.triggered.connect(self.home_deltatau_faststages) + self.actionUser_Storage.triggered.connect(self.update_user_and_storage) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_L), self) + self.shortcut.activated.connect(self.roi_add_line) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_R), self) + self.shortcut.activated.connect(self.roi_add_rect) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_M), self) + self.shortcut.activated.connect(self.toggle_mouse_tracking) + + self.shortcut = QShortcut(QKeySequence(Qt.Key_F11), self) + self.shortcut.activated.connect(self.toggle_maximized) + + self.shortcut = QShortcut(QKeySequence(Qt.Key_F12), self) + self.shortcut.activated.connect(self.show_window_configuration) + + self.shortcut = QShortcut(QKeySequence(Qt.Key_F5), self) + self.shortcut.activated.connect(self.generic_dialog) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_B), self) + self.shortcut.activated.connect(lambda: self._beammark.toggle_handle()) + + self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_P), self) + self.shortcut.activated.connect(self.camera_pause_toggle) + + # adding a menu entry to one of the menus + action = QAction("Beam Marker Size", self) + action.setToolTip("Update the beam marker size on GUI; *not* the actual beam size!") + action.setStatusTip("Update the beam marker size on GUI; *not* the actual beam size!") + action.triggered.connect(self.set_beam_size_marker_dialog) + self.menuAdvanced.addAction(action) + + action = QAction("Backlight Reference Positions", self) + action.setToolTip("Update the reference positions for the backlight") + action.setStatusTip("Update the reference positions for the backlight") + action.triggered.connect(self.set_backlight_positions_dialog) + self.menuAdvanced.addAction(action) + + action = QAction("Collimator Reference Positions", self) + action.setToolTip("Update the reference positions for the collimator") + action.setStatusTip("Update the reference positions for the collimator") + action.triggered.connect(self.set_collimator_reference_positions) + self.menuAdvanced.addAction(action) + + action = QAction("Cryojet Reference Positions", self) + action.setToolTip("Update the reference positions for the cryojet nozzle position") + action.setStatusTip("Update the reference positions for the cryojet nozzle position") + action.triggered.connect(self.set_cryojet_positions_dialog) + self.menuAdvanced.addAction(action) + + action = QAction("Post sample tube Reference Positions", self) + action.setToolTip("Update the reference positions for the post tube") + action.setStatusTip("Update the reference positions for the post tube") + action.triggered.connect(self.set_posttube_references_dialog) + self.menuAdvanced.addAction(action) + + action = QAction("Delta Tau Parameters", self) + action.setToolTip("Parameters affecting the Delta Tau") + action.setStatusTip("Parameters affecting the Delta Tau") + action.triggered.connect(self.set_deltatau_parameters) + self.menuAdvanced.addAction(action) + + action = QAction("Tell Mount Positions", self) + action.setToolTip("Mount positions for TELL sample changer") + action.setStatusTip("Parameters affecting the Delta Tau") + action.triggered.connect(self.set_tell_mount_positions) + self.menuAdvanced.addAction(action) + + def daq_method_prelocated_remove_markers(self): + try: + for m in self._marker_rois: + m.disconnect_signals() + self.vb.removeItem(m) + except Exception as e: + _log.warning("maybe failed removing markers: {}".format(e)) + self._marker_rois = [] + + def pause_all_markers(self): + for m in self._marker_rois: + m.disconnect_signals() + + def resume_all_markers(self): + for m in self._marker_rois: + m.reconnect_signals() + + def daq_method_prelocated_update_markers(self): + self.daq_method_prelocated_remove_markers() + data = self.prelocationModule.get_data() + add_xtals = self.prelocationModule._xtals_transformed + draw_xtals = self.prelocationModule.set_draw_crystal_marks + vb = self.vb + self._marker_rois = [] + ppm = self.getPpm() + for n, cc in enumerate(data): + is_fiducial, gx, gy, cx, cy, ox, oy = cc + if not is_fiducial: + if not (add_xtals and draw_xtals): + continue + _log.debug(f"adding {'fiducial' if is_fiducial else 'xtal'} mark #{n}: {is_fiducial} {gx:.3f}, {gy:.3f}, {cx:.1f}, {cy:.1f}") + m = CrystalCircle( + gonio_xy=(gx, gy), + parent=self, + model_row_index=n, + is_fiducial=is_fiducial, + ppm=ppm, + ) + # m.sigRegionChangeFinished.connect(lambda roi=m: self.daq_method_prelocated_update_model(roi)) + self._marker_rois.append(m) + vb.addItem(m) + for c in self._marker_rois: + c.reconnect_signals() + c.follow_stage() + + def daq_method_prelocated_set_fiducial(self, camx, camy, gx, gy): + _log.debug(f"camx, camy: {camx}, {camy}, fx, fy: {gx, gy}") + self.prelocationModule.set_fiducial_coords(camx, camy, gx, gy) + + def daq_method_prelocated_append_data(self, x, y, gx, gy): + _log.debug("appending to model: {} {}".format((x, y), (gx, gy))) + self.prelocationModule.append_data((x, y, gx, gy)) + self.daq_method_prelocated_update_markers() + + def daq_method_prelocated_update_model(self, roi): + row = roi.get_model_row() + pos = roi.pos() + self.prelocationModule.set_data_camera(row, pos) + _log.debug("updating row {} => {}".format(row, pos)) + + def daq_method_prelocated_add_reference(self): + self._references.append(pg.CircleROI()) + + def build_daq_methods_prelocated_tab(self): + tab = self._tab_daqmethod_prelocated + self.prelocationModule = PrelocatedCoordinatesModel.PrelocatedCoordinates(parent=self) + tab.layout().addWidget(self.prelocationModule) + self.prelocationModule.prefixSelected.connect(lambda prefix: self._le_prefix.setText(prefix)) + self.prelocationModule.dataFileLoaded.connect(self.daq_method_prelocated_update_markers) + self.prelocationModule.prelocatedDataUpdated.connect(self.daq_method_prelocated_update_markers) + self.prelocationModule.markersDeleted.connect(self.daq_method_prelocated_remove_markers) + self.fiducialPositionSelected.connect(self.daq_method_prelocated_set_fiducial) + self.appendPrelocatedPosition.connect(self.daq_method_prelocated_append_data) + self.prelocationModule.moveFastStageRequest.connect(self.move_fast_stage) + self._preloc_inspect_area = QPlainTextEdit() + tab.layout().addWidget(self._preloc_inspect_area) + + def move_fast_stage(self, x, y): + _log.info(f"received request to move gonio to x, y = {x:.3f}, {y:.3f} mm") + fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers() + fx_motor.move_motor_to_position(x) + fy_motor.move_motor_to_position(y) + + def set_beam_size_marker_dialog(self): + w, h = settings.value(BEAM_SIZE) + d = GenericDialog( + title="Beamsize", + message="Enter the size of the beam in microns", + inputs={ + "width": ( + "Width", + int(w), + Spinner(int(w), min=1, max=200, suffix=" \u00B5m"), + ), + "height": ( + "Height", + int(h), + Spinner(int(h), min=1, max=200, suffix=" \u00B5m"), + ), + }, + ) + if d.exec(): + results = d.results + _log.info("Updating beamsize to {}".format(results)) + w, h = results["width"], results["height"] + _log.debug("types {}".format(type(w))) + settings.setValue(BEAM_SIZE, (w, h)) + self._beammark.set_beam_size((w, h)) + settings.sync() + + def set_posttube_references_dialog(self): + x_up = settings.value("post_sample_tube/x_up", 0.0) + y_up = settings.value("post_sample_tube/y_up", 0.0) + x_down = settings.value("post_sample_tube/x_down", 0.0) + y_down = settings.value("post_sample_tube/y_down", 0.0) + dx = settings.value("post_sample_tube/dx", 0.0) + dy = settings.value("post_sample_tube/dy", 0.0) + tz_in = settings.value("post_sample_tube/z_in", 0.0) + tz_out = settings.value("post_sample_tube/z_out", 0.0) + + d = GenericDialog( + title="Post Sample Tube Configuration", + message="Enter the relative displacements for X and Y to move the post sample tube either in or out.", + inputs={ + "post_sample_tube/x_up": ( + "Up X", + float(x_up), + Spinner(float(x_up), decimals=3, min=-45.0, max=15.0, suffix=" mm"), + ), + "post_sample_tube/y_up": ( + "Up Y", + float(y_up), + Spinner(float(y_up), decimals=3, min=-45.0, max=15.0, suffix=" mm"), + ), + "post_sample_tube/x_down": ( + "Down X", + float(x_down), + Spinner( + float(x_down), decimals=3, min=-45.0, max=15.0, suffix=" mm" + ), + ), + "post_sample_tube/y_down": ( + "Down Y", + float(y_down), + Spinner( + float(y_down), decimals=3, min=-45.0, max=15.0, suffix=" mm" + ), + ), + "post_sample_tube/dx": ( + "out delta X", + float(dx), + Spinner(float(dx), decimals=3, min=-32.0, max=32.0, suffix=" mm"), + ), + "post_sample_tube/dy": ( + "out delta Y", + float(dy), + Spinner(float(dy), decimals=3, min=-32.0, max=32.0, suffix=" mm"), + ), + "post_sample_tube/z_in": ( + "tube Z in position", + float(tz_in), + Spinner(float(tz_in), decimals=3, min=-8.0, max=1.0, suffix=" mm"), + ), + "post_sample_tube/z_out": ( + "tube Z OUT position", + float(tz_out), + Spinner(float(tz_out), decimals=3, min=-8.0, max=1.0, suffix=" mm"), + ), + }, + ) + if d.exec(): + results = d.results + _log.info("setting post-sample-tube displacements {}".format(results)) + for k, v in results.items(): + settings.setValue(k, v) + settings.sync() + + def set_collimator_reference_positions(self): + x_out = float(settings.value("collimator/dx", 0.0)) + y_out = float(settings.value("collimator/dy", 0.0)) + x_in = float(settings.value("collimator/x_in", 0.0)) + y_in = float(settings.value("collimator/y_in", 0.0)) + d = GenericDialog( + title="Collimator configuration", + message="Enter reference positions for the collimator", + inputs={ + "collimator/dx": ( + "Collimator out deltaX", + x_out, + Spinner(x_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"), + ), + "collimator/dy": ( + "Collimator out deltaY", + y_out, + Spinner(y_out, decimals=3, min=-15.9, max=15.9, suffix=" mm"), + ), + "collimator/x_in": ( + "Collimator in X", + x_in, + Spinner(x_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"), + ), + "collimator/y_in": ( + "Collimator in Y", + y_in, + Spinner(y_in, decimals=3, min=-15.9, max=15.9, suffix=" mm"), + ), + }, + ) + if d.exec(): + results = d.results + _log.info("setting collimator reference positions {}".format(results)) + for k, v in results.items(): + settings.setValue(k, v) + settings.sync() + + def set_backlight_positions_dialog(self): + p_in = int(settings.value("backlight/in")) + p_out = int(settings.value("backlight/out")) + p_diode = int(settings.value("backlight/diode")) + d = GenericDialog( + title="Back Light configuration", + message="Enter reference positions for the backlight", + inputs={ + "backlight/in": ( + "In position", + p_in, + Spinner(p_in, min=-30000, max=10), + ), + "backlight/out": ( + "Out position", + p_out, + Spinner(p_out, min=-1000, max=10), + ), + "backlight/diode": ( + "Diode position", + p_diode, + Spinner(p_diode, min=-40000, max=-20000), + ), + }, + ) + if d.exec(): + results = d.results + _log.info("setting back light reference positions {}".format(results)) + for k, v in results.items(): + settings.setValue(k, v) + settings.sync() + + def set_cryojet_positions_dialog(self): + p_in = settings.value(CRYOJET_NOZZLE_IN, type=float) + p_out = settings.value(CRYOJET_NOZZLE_OUT, type=float) + motion_enabled = option(CRYOJET_MOTION_ENABLED) + d = GenericDialog( + title="Cryojet Nozzle Configuration", + message="Enter reference positions for the cryojet nozzle position", + inputs={ + CRYOJET_NOZZLE_IN: ("In position", p_in, Spinner(p_in, min=3, max=15)), + CRYOJET_NOZZLE_OUT: ("Out position",p_out,Spinner(p_out, min=-1000, max=10),), + CRYOJET_MOTION_ENABLED: ("Move Cryojet in Transitions",motion_enabled,Checkbox(motion_enabled, "move cryojet"),), + }, + ) + if d.exec(): + results = d.results + _log.info("setting cryojet reference positions {}".format(results)) + for k, v in results.items(): + settings.setValue(k, v) + settings.sync() + + def set_deltatau_parameters(self): + a = settings.value(DELTATAU_VELOCITY_SCALE, 1, type=float) + b = option(DELTATAU_SORT_POINTS) + c = option(DELTATAU_OMEGACOS) + d = option(DELTATAU_SHOW_PLOTS) + + d = GenericDialog( + title="Delta Tau Parameters", + message="These parameters affect the data collection.", + inputs={ + DELTATAU_VELOCITY_SCALE: ("Velocity Scale (1=optimal, 0=zero vel at target)", a, + Spinner(a, min=0, max=1, suffix=""),), + DELTATAU_SORT_POINTS: ("Sort pointshoot/prelocated coords", b, + Checkbox(b, "sort points"),), + DELTATAU_SHOW_PLOTS: ("show plots after collection", d, + Checkbox(d, "correct"),), + DELTATAU_OMEGACOS: ( "preloc. correct omega tilt", c, + Checkbox(c, "correct"),), + }, + ) + if d.exec(): + results = d.results + _log.info("setting delta tau parameters {}".format(results)) + for k, v in results.items(): + settings.setValue(k, v) + settings.sync() + + def set_tell_mount_positions(self): + AUTODRY_ENABLED = "tell/autodry_enabled" + AUTODRY_MAXMOUNTS = "tell/autodry_max_number_of_mounts" + AUTODRY_MAXTIME = "tell/autodry_max_time" + + SECS_HOURS = 60 * 60 + + enabled = option(AUTODRY_ENABLED) + maxtime = settings.value(AUTODRY_MAXTIME, type=float) / SECS_HOURS + maxmounts = settings.value(AUTODRY_MAXMOUNTS, type=int) + + d = GenericDialog( + title="TELL Settings", + message="These control some features of the TELL sample changer", + inputs={ + AUTODRY_ENABLED: ("Auto dry", enabled, + Checkbox(enabled, "enabled")), + AUTODRY_MAXMOUNTS: ("Max. num. mounts between dry",maxmounts, + Spinner(maxmounts, decimals=0, min=1, max=100, suffix=" mounts"),), + AUTODRY_MAXTIME: ("Max. time between dry",maxtime, + Spinner(maxtime, decimals=1, min=0.5, max=5, suffix=" hours"),), + }, + ) + if d.exec(): + results = d.results + _log.info("setting tell parameters {}".format(results)) + for k, v in results.items(): + if k == AUTODRY_MAXTIME: + v = v * SECS_HOURS + settings.setValue(k, v) + settings.sync() + + def generic_dialog(self, title="test", message=""): + d = GenericDialog(title="Beamsize", message="some message") + if d.exec(): + results = d.results + print(results) + + def toggle_maximized(self): + if self.isMaximized(): + self.showNormal() + else: + self.showMaximized() + + def show_window_configuration(self): + _log.debug("maximized? {}".format(self.isMaximized())) + + def home_deltatau_faststages(self): + _log.warning("homing fast stages") + epics.PV("SAR-EXPMX1:ASYN.AOUT").put(b"enable plc 1") + + def update_user_and_storage(self): + global folders + pgroup = settings.value(EXPERIMENT_PGROUP, "not_set_yet") + diag = GenericDialog( title="P-group for experiment", message="Enter the p-group to be used", + inputs={EXPERIMENT_PGROUP: ("P-group", pgroup, QLineEdit(pgroup))}, use_buttons=False,) + diag.setModal(True) + + run_user = getpass.getuser() + + pgrp_re = re.compile(r"^p(?P\d{5})$") + + if not self.is_recently_active(): + diag.exec() + results = diag.results + for k, v in results.items(): + m = pgrp_re.match(v) + if m: + settings.setValue(k, v) + settings.setValue(EXPERIMENT_UID, int(m["uid"])) else: - self.showMaximized() + QMessageBox.critical(self, "wrong P-group format", "P-groups are in the format:\n\n\t\tp?????\n\n\twhere ? = digit",) + self.update_user_and_storage() + return + settings.sync() + folders.set_pgroup(settings.value(EXPERIMENT_PGROUP)) + try: + folders.check_permissons() + except: + folder = folders.pgroup_folder + pgroup = settings.value(EXPERIMENT_PGROUP) + box = QMessageBox() + box.setText("No Write Permission!") + box.setInformativeText("User {} has no write access to p-group {} folder:\n ➜ {}\n".format(run_user, pgroup, folder)) + box.setIcon(QMessageBox.Warning) + box.setDetailedText("The folder /sf/bernina/data/{pgroup}/res/ has to writable by the user {user}, currently running the SwissMX app.".format(pgroup=pgroup, user=run_user)) + btIgnore = box.addButton("Ignore", QMessageBox.NoRole) + btRetry = box.addButton("Le'me try again", QMessageBox.YesRole) + box.exec_() + ans = box.clickedButton() + if ans == btRetry: + self.update_user_and_storage() + elif ans == btIgnore: + _log.warning("no write access to pgroup but user didn't care") - def show_window_configuration(self): - _log.debug("maximized? {}".format(self.isMaximized())) + self._label_pgroup.setText(settings.value(EXPERIMENT_PGROUP)) - def home_deltatau_faststages(self): - _log.warning("homing fast stages") - epics.PV("SAR-EXPMX1:ASYN.AOUT").put(b"enable plc 1") + def init_settings(self): + app = QApplication.instance() + # settings = Settings + #global folders + s = settings + keys = s.allKeys() - def update_user_and_storage(self): - global folders - pgroup = settings.value(EXPERIMENT_PGROUP, "not_set_yet") - diag = GenericDialog( title="P-group for experiment", message="Enter the p-group to be used", - inputs={EXPERIMENT_PGROUP: ("P-group", pgroup, QLineEdit(pgroup))}, use_buttons=False,) - diag.setModal(True) + if ACTIVATE_PULSE_PICKER not in keys: + settings.setValue(ACTIVATE_PULSE_PICKER, False) - run_user = getpass.getuser() + if SKIP_ESCAPE_TRANSITIONS_IF_SAFE not in keys: + settings.setValue(SKIP_ESCAPE_TRANSITIONS_IF_SAFE, False) - pgrp_re = re.compile(r"^p(?P\d{5})$") + if EXPERIMENT_PGROUP not in keys: + self.update_user_and_storage() - if not self.is_recently_active(): - diag.exec() - results = diag.results - for k, v in results.items(): - m = pgrp_re.match(v) - if m: - settings.setValue(k, v) - settings.setValue(EXPERIMENT_UID, int(m["uid"])) - else: - QMessageBox.critical(self, "wrong P-group format", "P-groups are in the format:\n\n\t\tp?????\n\n\twhere ? = digit",) - self.update_user_and_storage() - return - settings.sync() - folders.set_pgroup(settings.value(EXPERIMENT_PGROUP)) - try: - folders.check_permissons() - except: - folder = folders.pgroup_folder - pgroup = settings.value(EXPERIMENT_PGROUP) - box = QMessageBox() - box.setText("No Write Permission!") - box.setInformativeText("User {} has no write access to p-group {} folder:\n ➜ {}\n".format(run_user, pgroup, folder)) - box.setIcon(QMessageBox.Warning) - box.setDetailedText("The folder /sf/bernina/data/{pgroup}/res/ has to writable by the user {user}, currently running the SwissMX app.".format(pgroup=pgroup, user=run_user)) - btIgnore = box.addButton("Ignore", QMessageBox.NoRole) - btRetry = box.addButton("Le'me try again", QMessageBox.YesRole) - box.exec_() - ans = box.clickedButton() - if ans == btRetry: - self.update_user_and_storage() - elif ans == btIgnore: - _log.warning("no write access to pgroup but user didn't care") + if "hits/marker_size" not in keys: + settings.setValue("hits/marker_size", 10) - self._label_pgroup.setText(settings.value(EXPERIMENT_PGROUP)) + if "window/geometry" in keys: + self.restoreGeometry(s.value("window/geometry", "")) + self.restoreState(s.value("window/windowState", "")) - def init_settings(self): - app = QApplication.instance() - # settings = Settings - #global folders - s = settings - keys = s.allKeys() + if "window/main_splitter" in keys: + sizes = [int(s) for s in s.value("window/main_splitter")] + self._main_splitter.setSizes(sizes) + else: + self._main_splitter.setSizes([500, 1200]) - if ACTIVATE_PULSE_PICKER not in keys: - settings.setValue(ACTIVATE_PULSE_PICKER, False) + if "graphs/show_auxiliary" not in keys: + s.setValue("graphs/show_auxiliary", False) - if SKIP_ESCAPE_TRANSITIONS_IF_SAFE not in keys: - settings.setValue(SKIP_ESCAPE_TRANSITIONS_IF_SAFE, False) + if "graphs/auxiliary_graph_index" not in keys: + s.setValue("graphs/auxiliary_graph_index", 0) - if EXPERIMENT_PGROUP not in keys: - self.update_user_and_storage() + if "scan_request/last_location" not in keys: + d10 = os.path.join(os.path.expanduser("~"), "Data10") + s.setValue("scan_request/last_location", d10) - if "hits/marker_size" not in keys: - settings.setValue("hits/marker_size", 10) + if BEAM_MARKER_POSITIONS in keys: + self._beam_markers = s.value(BEAM_MARKER_POSITIONS) + self.update_beam_marker_fitters() + _log.info("read beam markers {}".format(self._beam_markers)) - if "window/geometry" in keys: - self.restoreGeometry(s.value("window/geometry", "")) - self.restoreState(s.value("window/windowState", "")) + if BEAM_SIZE in keys: + _log.info("setting beamsize from stored settings: {}".format(s.value(BEAM_SIZE))) + else: + _log.warning("beam size may not reflect reality, please check") + s.setValue(BEAM_SIZE, (40, 20)) - if "window/main_splitter" in keys: - sizes = [int(s) for s in s.value("window/main_splitter")] - self._main_splitter.setSizes(sizes) - else: - self._main_splitter.setSizes([500, 1200]) + if CAMERA_TRANSFORMATIONS in keys: + app._camera.set_transformations(s.value(CAMERA_TRANSFORMATIONS)) - if "graphs/show_auxiliary" not in keys: - s.setValue("graphs/show_auxiliary", False) + if CAMERA_ZOOM_TO_PPM in keys: + self._zoom_to_ppm = s.value(CAMERA_ZOOM_TO_PPM) + _log.info(f"{CAMERA_ZOOM_TO_PPM} updated: {self._zoom_to_ppm}") + self.update_ppm_fitters() - if "graphs/auxiliary_graph_index" not in keys: - s.setValue("graphs/auxiliary_graph_index", 0) + def really_quit(self): + """called when user Ctrl-Q the app""" + global user, just_quit + if (just_quit + or QMessageBox.question(self, "", "Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) == QMessageBox.Yes + ): + self._do_quit = True + self.close() - if "scan_request/last_location" not in keys: - d10 = os.path.join(os.path.expanduser("~"), "Data10") - s.setValue("scan_request/last_location", d10) + def closeEvent(self, event): + """this is called when the user clicks the window's cross icon""" + global user, just_quit + if ( + just_quit + or self._do_quit + or QMessageBox.question( self, "", "Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) == QMessageBox.Yes + ): + sample_camera.disconnect() + settings.setValue("window/geometry", self.saveGeometry()) + settings.setValue("window/windowState", self.saveState()) + settings.setValue("window/main_splitter", self._main_splitter.sizes()) + settings.setValue("last_active", time.time()) - if BEAM_MARKER_POSITIONS in keys: - self._beam_markers = s.value(BEAM_MARKER_POSITIONS) - self.update_beam_marker_fitters() - _log.info("read beam markers {}".format(self._beam_markers)) + QMainWindow.closeEvent(self, event) + else: + event.ignore() - if BEAM_SIZE in keys: - _log.info("setting beamsize from stored settings: {}".format(s.value(BEAM_SIZE))) - else: - _log.warning("beam size may not reflect reality, please check") - s.setValue(BEAM_SIZE, (40, 20)) + def is_recently_active(self): + last_active = settings.value("last_active", 0, type=float) + minutes_since_last = int((time.time() - last_active) / 60.0) + return minutes_since_last < 60 - if CAMERA_TRANSFORMATIONS in keys: - app._camera.set_transformations(s.value(CAMERA_TRANSFORMATIONS)) + def openPreferencesDialog(self): + PreferencesDialog(self).exec_() - if CAMERA_ZOOM_TO_PPM in keys: - self._zoom_to_ppm = s.value(CAMERA_ZOOM_TO_PPM) - _log.info(f"{CAMERA_ZOOM_TO_PPM} updated: {self._zoom_to_ppm}") - self.update_ppm_fitters() + def set_app_icon(self): + scriptDir = os.path.dirname(os.path.realpath(__file__)) + self.setWindowIcon(QtGui.QIcon(os.path.join(scriptDir + os.path.sep + "logo.png"))) - def really_quit(self): - """called when user Ctrl-Q the app""" - global user, just_quit - if (just_quit - or 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 set_widget_background_color(self, color): + """change a widget's color + :param color: + :return: + """ + try: + color = QtGui.QColor.colorNames().index(color) + except: + return + p = self.palette() + p.setColor(self.backgroundRole(), color) + self.setPalette(p) - def closeEvent(self, event): - """this is called when the user clicks the window's cross icon""" - global user, just_quit - if ( - just_quit - or self._do_quit - or QMessageBox.question( self, "", "Are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No,) == QMessageBox.Yes - ): - sample_camera.disconnect() - settings.setValue("window/geometry", self.saveGeometry()) - settings.setValue("window/windowState", self.saveState()) - settings.setValue("window/main_splitter", self._main_splitter.sizes()) - settings.setValue("last_active", time.time()) - - QMainWindow.closeEvent(self, event) - else: - event.ignore() - - def is_recently_active(self): - last_active = settings.value("last_active", 0, type=float) - minutes_since_last = int((time.time() - last_active) / 60.0) - return minutes_since_last < 60 - - def openPreferencesDialog(self): - PreferencesDialog(self).exec_() - - def set_app_icon(self): - scriptDir = os.path.dirname(os.path.realpath(__file__)) - self.setWindowIcon(QtGui.QIcon(os.path.join(scriptDir + os.path.sep + "logo.png"))) - - def set_widget_background_color(self, color): - """change a widget's color - :param color: - :return: - """ - try: - color = QtGui.QColor.colorNames().index(color) - except: - return - p = self.palette() - p.setColor(self.backgroundRole(), color) - self.setPalette(p) - - def load_stylesheet(self): - with open("swissmx.css", "r") as sheet: - self.setStyleSheet(sheet.read()) + def load_stylesheet(self): + with open("swissmx.css", "r") as sheet: + self.setStyleSheet(sheet.read()) def sigint_handler(*args): - """Handler for the SIGINT signal.""" - app=QApplication.instance() - app.quit() + """Handler for the SIGINT signal.""" + app=QApplication.instance() + app.quit() def main(): - import argparse + import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0) - args = parser.parse_args() - _log.info('Arguments:{}'.format(args.__dict__)) + parser = argparse.ArgumentParser() + parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0) + args = parser.parse_args() + _log.info('Arguments:{}'.format(args.__dict__)) - # needed so pycharm can restart application - signal.signal(signal.SIGINT, sigint_handler) + # needed so pycharm can restart application + signal.signal(signal.SIGINT, sigint_handler) - from PyQt5.QtWidgets import QApplication + from PyQt5.QtWidgets import QApplication - # set app icon + # set app icon - app = QApplication(sys.argv) + app = QApplication(sys.argv) - if args.sim&0x01: - app._backlight = backlight.Backlight(None) - else: - app._backlight = backlight.Backlight() - if args.sim&0x02: - app._illumination = illumination.IlluminationControl(None) - else: - app._illumination = illumination.IlluminationControl() - if args.sim&0x04: - app._zoom = zoom.QopticZoom(None) - else: - app._zoom = zoom.QopticZoom() - if args.sim&0x08: - app._camera = camera.epics_cam(None) - app._camera.sim_gen(mode=0) - app._camera.run() - else: - app._camera = camera.epics_cam() - app._camera.run() + if args.sim&0x01: + app._backlight = backlight.Backlight(None) + else: + app._backlight = backlight.Backlight() + if args.sim&0x02: + app._illumination = illumination.IlluminationControl(None) + else: + app._illumination = illumination.IlluminationControl() + if args.sim&0x04: + app._zoom = zoom.QopticZoom(None) + else: + app._zoom = zoom.QopticZoom() + if args.sim&0x08: + app._camera = camera.epics_cam(None) + app._camera.sim_gen(mode=0) + app._camera.run() + else: + app._camera = camera.epics_cam() + app._camera.run() - 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 = 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) - splash_pix = QPixmap("artwork/logo/256x256.png") - splash = QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint) - splash.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) - splash.setEnabled(False) + splash_pix = QPixmap("artwork/logo/256x256.png") + splash = QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint) + splash.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) + splash.setEnabled(False) - progressBar = QProgressBar(splash) - progressBar.setMaximum(10) - progressBar.setGeometry(0, splash_pix.height() - 50, splash_pix.width(), 20) + progressBar = QProgressBar(splash) + progressBar.setMaximum(10) + progressBar.setGeometry(0, splash_pix.height() - 50, splash_pix.width(), 20) - splash.show() - # splash.showMessage("

Solid support scanning brought to you by

", Qt.AlignTop | Qt.AlignCenter, Qt.black) + splash.show() + # splash.showMessage("

Solid support scanning brought to you by

", Qt.AlignTop | Qt.AlignCenter, Qt.black) - for i in range(1, 11): - progressBar.setValue(i) - t = time.time() - while time.time() < t + 0.1: - app.processEvents() + for i in range(1, 11): + progressBar.setValue(i) + t = time.time() + while time.time() < t + 0.1: + app.processEvents() - main = Main() - main.show() - splash.finish(main) - #main.update_user_and_storage() #ZAC: orig. code - sys.exit(app.exec_()) + main = Main() + main.show() + splash.finish(main) + #main.update_user_and_storage() #ZAC: orig. code + sys.exit(app.exec_()) if __name__ == "__main__": - os.environ['EPICS_CA_ADDR_LIST'] ='129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066' - main() + os.environ['EPICS_CA_ADDR_LIST'] ='129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066' + main()