367 lines
11 KiB
Python
Executable File
367 lines
11 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# *-----------------------------------------------------------------------*
|
|
# | |
|
|
# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
|
|
# | Based on Zac great first implementation |
|
|
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
|
|
# *-----------------------------------------------------------------------*
|
|
'''
|
|
'''
|
|
|
|
import json
|
|
import logging
|
|
_log=logging.getLogger(__name__)
|
|
import os
|
|
import qtawesome
|
|
from PyQt5 import QtWidgets, uic
|
|
import PyQt5.QtWidgets as qtw
|
|
from PyQt5.QtCore import pyqtSignal
|
|
from PyQt5.QtWidgets import (
|
|
QApplication,
|
|
QPushButton,
|
|
QGroupBox,
|
|
QGridLayout,
|
|
QLabel,
|
|
QDoubleSpinBox,
|
|
QComboBox,
|
|
QSpinBox,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QCheckBox,
|
|
)
|
|
from PyQt5.uic import loadUiType
|
|
import epics
|
|
|
|
Ui_Zoom, QWidget = loadUiType("zoom.ui")
|
|
MIN_ZOOM = 1
|
|
MAX_ZOOM = 1000
|
|
SPINNER_SINGLE_STEP = 50
|
|
SPINNER_LARGE_STEP = 200
|
|
|
|
class Zoom(QGroupBox, Ui_Zoom):
|
|
zoomChanged = pyqtSignal(float)
|
|
moveBacklight = pyqtSignal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super(Zoom, self).__init__(parent=parent)
|
|
self.setupUi(self)
|
|
self.setTitle("Sample Viewing")
|
|
|
|
def configure(self):
|
|
keys = settings.allKeys()
|
|
if "sample_viewing/zoom_buttons" not in keys:
|
|
settings.setValue("sample_viewing/zoom_buttons",
|
|
json.dumps([(1, "1"),(200, "200"),(400, "400"),(600, "600"),(800, "800"),(1000, "1000"),]),)
|
|
buttons = json.loads(settings.value("sample_viewing/zoom_buttons"))
|
|
|
|
if "backlight/backlight_pv" not in keys:
|
|
settings.setValue("backlight/backlight_pv", "SAR-EXPMX:MOT_BLGT")
|
|
backlight_pv = settings.value("backlight/backlight_pv")
|
|
|
|
if "sample_viewing/zoom_api" not in keys:
|
|
settings.setValue("sample_viewing/zoom_api", "rest://pc12818.psi.ch:9999")
|
|
zoom_api = settings.value("sample_viewing/zoom_api")
|
|
|
|
#self.get_zoom_pv = PV(zoom_api + ":ZOOM-RBV", callback=self.zoom_update_cb)
|
|
#self.status_pv = PV(zoom_api + ":ZOOM-STATUS", callback=self.zoom_status_cb)
|
|
current_zoom_value = self.get_zoom()
|
|
|
|
# zoom widgets
|
|
layout = QVBoxLayout()
|
|
layout.setSpacing(0)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
self._button_box.setLayout(layout)
|
|
self._zoom_spinner = QSpinBox()
|
|
self._zoom_spinner.setRange(MIN_ZOOM, MAX_ZOOM)
|
|
self._zoom_spinner.setValue(current_zoom_value)
|
|
self._zoom_spinner.editingFinished.connect(self.move_zoom_a)
|
|
# self._zoom_spinner.setSingleStep(SPINNER_SINGLE_STEP)
|
|
self._zoom_spinner.setSingleStep(SPINNER_LARGE_STEP)
|
|
_box = QWidget()
|
|
_box.setLayout(QHBoxLayout())
|
|
_box.layout().setSpacing(0)
|
|
_box.layout().setContentsMargins(0, 0, 0, 0)
|
|
_box.layout().addWidget(QLabel("Zoom Level"))
|
|
_box.layout().addWidget(self._zoom_spinner)
|
|
layout.addWidget(_box)
|
|
_box = QWidget()
|
|
_box.setLayout(QGridLayout())
|
|
_box.layout().setContentsMargins(0, 0, 0, 0)
|
|
for n, b in enumerate(buttons):
|
|
row, col = n // 2, n % 2
|
|
value, label = b
|
|
but = QPushButton(label)
|
|
but.setCheckable(True)
|
|
but.setChecked(value == current_zoom_value)
|
|
but.setAutoExclusive(True)
|
|
but.setAccessibleName("zoom_button")
|
|
but.setObjectName("zoom_{}".format(value)) # used with findChild()
|
|
but.pressed.connect(lambda x=value: self.move_zoom(x))
|
|
_box.layout().addWidget(but, row, col, 1, 1)
|
|
self._zoom_buttonbox = _box
|
|
layout.addWidget(_box)
|
|
|
|
# backlight
|
|
self._top_grid.setLayout(QVBoxLayout())
|
|
lbox = QWidget()
|
|
lbox.setLayout(QHBoxLayout())
|
|
lbox.layout().setSpacing(0)
|
|
lbox.layout().setContentsMargins(0, 0, 0, 0)
|
|
self._top_grid.layout().addWidget(lbox)
|
|
#TODO: self.blgt_button = QPushButton(qtawesome.icon("material.lightbulb_outline"), "Backlight")
|
|
self.blgt_button = QPushButton( "Backlight")
|
|
self.blgt_button.clicked.connect(self.toggle_backlight)
|
|
|
|
lbox = QWidget()
|
|
lbox.setLayout(QHBoxLayout())
|
|
lbox.layout().setSpacing(0)
|
|
lbox.layout().setContentsMargins(0, 0, 0, 0)
|
|
self._top_grid.layout().addWidget(lbox)
|
|
|
|
lbox.layout().addWidget(self.blgt_button)
|
|
leds=QApplication.instance()._illumination
|
|
but = QCheckBox("Back")
|
|
but.setChecked(leds.is_on(illumination.back))
|
|
but.stateChanged.connect(self.back_toggle)
|
|
lbox.layout().addWidget(but)
|
|
|
|
but = QCheckBox("Front")
|
|
but.setChecked(leds.is_on(illumination.front))
|
|
but.stateChanged.connect(self.front_toggle)
|
|
lbox.layout().addWidget(but)
|
|
|
|
lbox = QWidget()
|
|
lbox.setLayout(QHBoxLayout())
|
|
lbox.layout().setSpacing(0)
|
|
lbox.layout().setContentsMargins(0, 0, 0, 0)
|
|
self._top_grid.layout().addWidget(lbox)
|
|
|
|
but = QCheckBox("Red")
|
|
but.setChecked(leds.is_on(illumination.red))
|
|
but.stateChanged.connect(self.red_toggle)
|
|
lbox.layout().addWidget(but)
|
|
|
|
but = QCheckBox("Green")
|
|
but.setChecked(leds.is_on(illumination.green))
|
|
but.stateChanged.connect(self.green_toggle)
|
|
lbox.layout().addWidget(but)
|
|
|
|
but = QCheckBox("Blue")
|
|
but.setChecked(leds.is_on(illumination.blue))
|
|
but.stateChanged.connect(self.blue_toggle)
|
|
lbox.layout().addWidget(but)
|
|
|
|
lbox = QWidget()
|
|
lbox.setLayout(QHBoxLayout())
|
|
lbox.layout().setSpacing(0)
|
|
lbox.layout().setContentsMargins(0, 0, 0, 0)
|
|
self._top_grid.layout().addWidget(lbox)
|
|
|
|
self.slid = None
|
|
|
|
cam=QApplication.instance()._camera
|
|
grp = QGroupBox("Camera settings")
|
|
grid = QGridLayout()
|
|
grp.setLayout(grid)
|
|
lab = QLabel("Exposure (ms)")
|
|
grid.addWidget(lab, 0, 0)
|
|
self.slid = QDoubleSpinBox()
|
|
self.slid.setValue(cam.get_exposure())
|
|
self.slid.setMinimum(0.001)
|
|
self.slid.setMaximum(1000.000)
|
|
self.slid.setDecimals(3)
|
|
self.slid.setSuffix(" ms")
|
|
grid.addWidget(self.slid, 0, 1)
|
|
|
|
#grid.addWidget(QLabel("Acq.Mode"), 1, 0)
|
|
#cbox = QComboBox()
|
|
#for mode in Camera.AcquisitionMode:
|
|
# cbox.addItem(mode.name, mode)
|
|
#cbox.setCurrentIndex(cam.pv_acqmode.get())
|
|
#cbox.currentIndexChanged.connect(self.update_camera_acqmode)
|
|
#grid.addWidget(cbox, 1, 1)
|
|
|
|
lbox.layout().addWidget(grp)
|
|
self.slid.editingFinished.connect(lambda: self.update_exposure(self.slid.value()))
|
|
|
|
def back_toggle(self, state):
|
|
leds=QApplication.instance()._illumination
|
|
leds.back(state)
|
|
|
|
def front_toggle(self, state):
|
|
leds=QApplication.instance()._illumination
|
|
leds.front(state)
|
|
|
|
def red_toggle(self, state):
|
|
leds=QApplication.instance()._illumination
|
|
leds.red(state)
|
|
|
|
def green_toggle(self, state):
|
|
leds=QApplication.instance()._illumination
|
|
leds.green(state)
|
|
|
|
def blue_toggle(self, state):
|
|
leds=QApplication.instance()._illumination
|
|
leds.blue(state)
|
|
|
|
def toggle_backlight(self):
|
|
bl=QApplication.instance()._backlight
|
|
if bl.is_pos('in'):
|
|
self.moveBacklight.emit("out")
|
|
self.blgt_button.setText("Move Backlight IN")
|
|
else:
|
|
self.moveBacklight.emit("in")
|
|
self.blgt_button.setText("Move Backlight OUT")
|
|
|
|
def update_camera_exposure(self, nv):
|
|
cam=QApplication.instance()._camera
|
|
cam.set_exposure(nv)
|
|
|
|
def update_exposure(self, nv):
|
|
_log.debug("update_exposure: {} {}".format(nv, self.slid.singleStep()))
|
|
if nv > 100:
|
|
self.slid.setSingleStep(50)
|
|
elif 100 > nv > 50:
|
|
self.slid.setSingleStep(10)
|
|
elif 50 > nv > 10:
|
|
self.slid.setSingleStep(5)
|
|
elif 10 > nv > 2:
|
|
self.slid.setSingleStep(1)
|
|
elif 2 > nv > 1.19:
|
|
self.slid.setSingleStep(0.2)
|
|
elif 1 > nv > 0.1:
|
|
self.slid.setSingleStep(0.1)
|
|
else:
|
|
self.slid.setSingleStep(0.01)
|
|
_log.debug("update_exposure: {} {}".format(nv, self.slid.singleStep()))
|
|
if nv > 0.001:
|
|
cam = QApplication.instance()._camera
|
|
cam.set_exposure(nv)
|
|
|
|
def update_camera_acqmode(self, index):
|
|
_log.debug("changing camera mode: {}".format(index))
|
|
cam=QApplication.instance()._camera
|
|
cam.auto(index)
|
|
|
|
def move_zoom_a(self):
|
|
nval = self._zoom_spinner.value()
|
|
self.move_zoom(nval)
|
|
|
|
def move_zoom(self, value: int):
|
|
zoom=QApplication.instance()._zoom
|
|
_log.debug("zooming to : {}".format(value))
|
|
self._zoom_spinner.blockSignals(True)
|
|
zoom.set(value)
|
|
but = "zoom_{:d}".format(value)
|
|
_log.debug("looking for button: {}".format(but))
|
|
for bbut in self._zoom_buttonbox.findChildren(QPushButton):
|
|
bbut.blockSignals(True)
|
|
curbut = bbut.objectName()
|
|
checked = curbut == but
|
|
bbut.setChecked(checked) # FIXME this is not working
|
|
bbut.blockSignals(False)
|
|
|
|
self._zoom_spinner.setValue(int(value))
|
|
self._zoom_spinner.blockSignals(False)
|
|
self.zoomChanged.emit(float(value))
|
|
|
|
def get_zoom(self) -> int:
|
|
zoom=QApplication.instance()._zoom
|
|
pos = zoom.get()
|
|
_log.debug("get_zoom(epics) => {}".format(pos))
|
|
return pos
|
|
|
|
def zoom_update_cb(self, pvname, value, char_value, **kwargs):
|
|
self._zoom_spinner.blockSignals(True)
|
|
self._zoom_spinner.setValue(value)
|
|
self._zoom_spinner.blockSignals(False)
|
|
|
|
def zoom_status_cb(self, pvname, value, char_value, **kwargs):
|
|
busy = bool(value)
|
|
self.setDisabled(busy)
|
|
|
|
|
|
class QopticZoom(object):
|
|
|
|
def __init__(self, prefix='SAR-EXPMX-FETURA'):
|
|
if prefix is None:
|
|
self._val=1
|
|
return #simulated mode
|
|
self._pv=pv=dict()
|
|
if prefix[-1]!=':': prefix+=':'
|
|
self._prefix=prefix
|
|
|
|
def getPv(self,name):
|
|
try:
|
|
pv=self._pv[name]
|
|
except KeyError:
|
|
prefix=self._prefix
|
|
pv=epics.PV(prefix+name)
|
|
self._pv[name]=pv
|
|
return pv
|
|
|
|
def get(self) -> int:
|
|
try:
|
|
pv = self.getPv('POS_RB')
|
|
except AttributeError:
|
|
val=self._val; _log.info('simulated mode:{}'.format(val))
|
|
return val
|
|
else:
|
|
pv=self.getPv('POS_RB')
|
|
return pv.get()
|
|
|
|
def set(self, val: int):
|
|
try:
|
|
pv=self.getPv('POS_SP')
|
|
except AttributeError:
|
|
_log.info('simulated mode:{}'.format(val))
|
|
self._val=val #simulated mode
|
|
else:
|
|
pv.put(val)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import time, os, PIL.Image, platform, subprocess
|
|
import argparse
|
|
logging.basicConfig(level=logging.DEBUG,format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--sim", "-s", help="simulate all devices", action='store_true')
|
|
args = parser.parse_args()
|
|
|
|
import sys
|
|
import backlight
|
|
import illumination
|
|
import camera
|
|
|
|
_log.info('Arguments:{}'.format(args.__dict__))
|
|
os.environ['EPICS_CA_ADDR_LIST'] = '129.129.244.255 sf-saresc-cagw.psi.ch:5062 sf-saresc-cagw.psi.ch:5066'
|
|
app = QApplication(sys.argv)
|
|
from app_config import settings, appsconf
|
|
|
|
#from PyQt5 import QtGui
|
|
#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")
|
|
|
|
if args.sim:
|
|
app._backlight = backlight.Backlight(None)
|
|
app._illumination = illumination.IlluminationControl(None)
|
|
app._zoom = QopticZoom(None)
|
|
app._camera = camera.epics_cam(None)
|
|
else:
|
|
app._backlight = backlight.Backlight()
|
|
app._illumination = illumination.IlluminationControl()
|
|
app._zoom = QopticZoom()
|
|
app._camera = camera.epics_cam()
|
|
|
|
|
|
|
|
simulated = appsconf["microscope"]["zoom"].get("simulate", False)
|
|
|
|
obj=Zoom()
|
|
obj.configure()
|
|
obj.show()
|
|
app.exec_()
|
|
|