From 189c98951a7a4110b856def62fffb7fd79f969a3 Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Fri, 9 Sep 2022 15:54:18 +0200 Subject: [PATCH] cleanup --- CustomROI.py | 595 --------------------------------------------- GenericDialog.py | 204 ---------------- ModuleFixTarget.py | 480 +++++++----------------------------- app_config.py | 36 ++- deltatau.py | 2 +- swissmx.py | 222 +++++++---------- swissmx.ui | 215 ++-------------- 7 files changed, 206 insertions(+), 1548 deletions(-) delete mode 100644 CustomROI.py delete mode 100644 GenericDialog.py diff --git a/CustomROI.py b/CustomROI.py deleted file mode 100644 index 20cf3f4..0000000 --- a/CustomROI.py +++ /dev/null @@ -1,595 +0,0 @@ -import logging - -from PyQt5 import QtGui, QtCore -from PyQt5.QtGui import QColor, QTransform -from PyQt5.QtWidgets import QMenu, QAction, QSpinBox, QMenu - -#from GenericDialog import GenericDialog #ZAC: orig. code -from pyqtgraph import ROI, Point, RectROI, mkPen -from pyqtgraph import functions as fn - -from app_config import AppCfg# settings - -from math import * - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -class Grid(ROI): - """a grid""" - - def __init__( - self, - x_step, - y_step, - x_offset, - y_offset, - grid_index=0, - parent=None, - gonio_xy=(0, 0), - **kargs - ): - self._grid_index = grid_index - self._size = None - self._pos = None - self._shape = None - ROI.__init__(self, (500, 500), (300, 300), **kargs) - self.parent = parent - - self.addScaleHandle([1, 1], [0, 0]) - self.addScaleHandle([0, 0], [1, 1]) - self.addScaleRotateHandle([1, 0], [0, 0]) - # self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) - - self._steps = x_step / 1000., y_step / 1000. - self._offsets = x_offset / 1000, y_offset / 1000 - self._beam_position = parent.get_beam_mark_on_camera_xy() - self.set_gonio_xy(*gonio_xy) - - self.setToolTip("grid") - - parent.pixelsPerMillimeter.connect(self.update_ppm) - parent.beamCameraCoordinatesChanged.connect(self.update_beam_position) - self.enable_stage_tracking() - - self._ppm = parent.getPpm() - self._mm_size = 300 / self._ppm, 300 / self._ppm - self._original_ppm = self._ppm # marker only works at original zoom - # because zoom axis is not aligned - # with viewing axis - self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY() - self.invertible = True - self.sigRegionChanged.connect(self.invalidate) - self.sigRegionChangeFinished.connect(self.calculate_gonio_xy) - self.scaleSnap = False - self.aspectLocked = False - self.translatable = True - self.rotateAllowed = False - self.removable = True - - self.follow_stage() - # self.calculate_gonio_xy() - - def enable_stage_tracking(self): - self.parent.fast_x_position.connect(self.follow_x) - self.parent.fast_y_position.connect(self.follow_y) - - def disable_stage_tracking(self): - self.parent.fast_x_position.disconnect(self.follow_x) - self.parent.fast_y_position.disconnect(self.follow_y) - - def follow_x(self, v): - self.follow_stage(fx=v) - - def follow_y(self, v): - self.follow_stage(fy=v) - - def removeClicked(self): - self.parent.pixelsPerMillimeter.disconnect(self.update_ppm) - self.parent.beamCameraCoordinatesChanged.disconnect(self.update_beam_position) - self.parent.fast_x_position.disconnect(self.follow_x) - self.parent.fast_y_position.disconnect(self.follow_y) - super(Grid, self).removeClicked() - - def calculate_gonio_xy(self): - self.update_grid_pos() - state = self.getState() - angle = state["angle"] - w, h = state["size"] - w, h = 1000 * w / self._ppm, 1000 * h / self._ppm - self._mm_size = w / 1000, h / 1000 - self.calculate_targets() - - def update_ppm(self, ppm): - self._ppm = ppm - self.update_grid_size(ppm) - self.update_grid_pos() - self.stateChanged() - - def update_grid_size(self, ppm, finish=False): - mmw, mmh = self._mm_size - w, h = mmw * ppm, mmh * ppm - self.setSize((w, h), update=False) - - def follow_stage(self, fx=None, fy=None): - self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY() - self.update_grid_pos() - self.stateChanged() - - def set_gonio_xy(self, x, y): - self._gx, self._gy = x, y - - def update_grid_pos(self): - idx = self._grid_index - ppm = self._ppm - bx, by = self.parent.get_beam_mark_on_camera_xy() - gx, gy = self._gx, self._gy # marker position in gonio coords - fx, fy = self._fx, self._fy - dx, dy = ( - ppm * (fx - gx), - ppm * (fy - gy), - ) # distance to beam in pixels for new ppm - x, y = bx - dx, by + dy - self.setPos(x, y, update=False) - mmw, mmh = self._mm_size - - def calculate_targets(self): - state = self.getState() - angle = radians(state["angle"]) - cosa = cos(angle) - sina = sin(angle) - w, h = state["size"] - xs, ys = self._steps - w, h = w / self._ppm, h / self._ppm # mm - gx, gy = self._gx, self._gy - nx = int(abs(w) / xs) - ny = int(abs(h) / ys) - points = [] - dx = xs - dy = ys - for yi in range(ny): - ly = yi * dy - y = gy + ly - for xi in range(nx): - lx = xi * dx - x = gx + lx - l = sqrt((lx) ** 2 + (ly) ** 2) - try: - alpha = acos((lx) / l) - except: - alpha = 0 - x2 = gx + l * cos(alpha + angle) - y2 = gy - l * sin(alpha + angle) - # print( - # "l={:8.3f} alpha={:.3f} xy={:8.3f}{:8.3f} xy2={:8.3f}{:8.3f}".format( - # l, degrees(alpha), x, y, x2, y2 - # ) - # ) - - points.append((x2, y2)) - self._grid_dimensions = (nx, ny) - self._points = points - self.parent.gridUpdated.emit(self._grid_index) - - def get_sorted_grid_targets(self): - pass - - def get_grid_targets(self): - return self._points - - def update_beam_position(self, pos): - self._beam_position = pos - - def invalidate(self): - state = self.getState() - x, y = state["pos"] - w, h = state["size"] - self._mm_size = w / self._ppm, h / self._ppm - fx, fy = self.camera2gonio(x, y) - self.set_gonio_xy(fx, fy) - self._shape = None - self.prepareGeometryChange() - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - w, h = self.getState()["size"] - if self._shape is None: - radius = self.getState()["size"][1] - p = QtGui.QPainterPath() - p.addRect(0, 0, w, h) - stroker = QtGui.QPainterPathStroker() - stroker.setWidth(10) - outline = stroker.createStroke(p) - self._shape = outline - return self._shape - - def paint(self, p, *args): - state = self.getState() - w, h = state["size"] - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(fn.mkPen(width=5, color=[200, 200, 100])) - p.drawRect(0, 0, w, h) - - p.setPen(fn.mkPen(width=3, color=[200, 100, 100])) - ppm = self._ppm - xs, ys = self._steps - w1, h1 = w / ppm, h / ppm # mm - nx = int(abs(w1) / xs) - ny = int(abs(h1) / ys) - sw = (xs) * ppm - sh = (ys) * ppm - # print((nx,ny), (w1, h1), (sw, sh)) - r = 10 - a, b = state["pos"] - if nx * ny > 1000: - return - for y in range(ny): - for x in range(nx): - p.drawEllipse((x * sw) - r, (y * sh) - r, 2 * r, 2 * r) - - def camera2gonio(self, x, y): - bx, by = self.parent.get_beam_mark_on_camera_xy() - ppm = self._ppm - dx, dy = (x - bx) / ppm, (y - by) / ppm - fx, fy = self._fx + dx, self._fy - dy - return fx, fy - - def gonio2camera(self, x, y): - ppm = self._ppm - bx, by = self.parent.get_beam_mark_on_camera_xy() - gx, gy = self._gx, self._gy # marker position in gonio coords - fx, fy = self._fx, self._fy - dx, dy = ( - ppm * (fx - gx), - ppm * (fy - gy), - ) # distance to beam in pixels for new ppm - x, y = bx - dx, by + dy - return x, y - - -class BeamMark(ROI): - """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" - - def __init__(self, pos=None, size=None, parent=None, **kargs): - if size is None: - size = [20, 20] - if pos is None: - pos = [0, 0] - self._size = size - self._pos = pos - self._shape = None - ROI.__init__(self, pos, size, **kargs) - - parent.pixelsPerMillimeter.connect(self.my_update_ppm) - parent.beamCameraCoordinatesChanged.connect(lambda x, y: self.setPos((x, y))) - - self._scale_handler = None - self.sigRegionChanged.connect(self.invalidate) - self.aspectLocked = False - self.translatable = False - self.rotateAllowed = False - self.invertible = False - self.removable = False - self.scaleSnap = True - self.snapSize = 1 - self.sigRegionChangeFinished.connect(self.show_size) - self.flip_ud() - - def show_size(self): - try: - ppm = self._ppm - except: - return - w, h = self.getState()["size"] - # convert to micrometer - nw, nh = (w * 1000) / ppm, (h * 1000) / ppm - logger.debug("persisting new beam marker size: {}".format((nw, nh))) - settings.setValue("beam/size", (nw, nh)) - - def toggle_handle(self): - if self._scale_handler is None: - logger.debug("adding beam marker resize handle") - self._scale_handler = self.addScaleHandle([0.5, 0.5], [0, 0]) - else: - logger.debug("removing beam marker resize handle") - self.removeHandle(self._scale_handler) - self._scale_handler = None - - def get_camera_beam_position(self): - return self.getState()["pos"] - - 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 (\u00B5)", int(w), QSpinBox()), - "height": ("Height (\u00B5)", int(h), QSpinBox()), - }, - ) - if d.exec(): - results = d.results - logger.info("Updating beamsize to {}".format(results)) - w, h = results["width"], results["height"] - logger.debug("types {}".format(type(w))) - settings.setValue("beam/size", (w, h)) - self.set_beam_size((w, h)) - - def flip_ud(self): - """flip up-down""" - t = self.transform() - 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() - t.setMatrix(m11, m12, m13, m21, -m22, m23, m31, m32, m33) - self.setTransform(t) - - def set_beam_size(self, size=None): - if size is not None: - self._size = size - ppm = self._ppm - w, h = self._size - nw, nh = (w / 1000) * ppm, (h / 1000) * ppm - self.setSize((nw, nh)) - self.setToolTip("Beamsize (W x H): {:.1f}\u00B5 x {:.1f}\u00B5".format(w, h)) - - def my_update_ppm(self, ppm): - self._ppm = ppm - self.set_beam_size() - - def invalidate(self): - self._shape = None - self.prepareGeometryChange() - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - w, h = self.size() - if self._shape is None: - p = QtGui.QPainterPath() - p.moveTo(Point(0, -h)) - p.lineTo(Point(0, h)) - p.moveTo(Point(-w, 0)) - p.lineTo(Point(w, 0)) - # - p.moveTo(Point(-w, -h)) - p.lineTo(Point(w, -h)) - p.lineTo(Point(w, h)) - p.lineTo(Point(-w, -h)) - stroker = QtGui.QPainterPathStroker() - outline = stroker.createStroke(p) - self._shape = outline - - return self._shape - - def paint(self, p, *args): - w, h = self.getState()["size"] - - v1, v2 = -(h * 0.8) / 2, (h * 0.8) / 2 - h1, h2 = -(w * 0.8) / 2, (w * 0.8) / 2 - - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(fn.mkPen(width=3, color=[200, 100, 100])) - p.drawLine(Point(0, v1), Point(0, v2)) - p.drawLine(Point(h1, 0), Point(h2, 0)) - - p.setPen(fn.mkPen(width=3, color=[200, 200, 100])) - p.drawRect(-w / 2, -h / 2, w, h) - # p.drawText(-w, -h, '{:.0f}x{:.0f}'.format(*self._size)) - - -class CrystalCircle(ROI): - """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" - - def __init__( - self, - pos=None, - size=None, - parent=None, - gonio_xy=(0, 0), - model_row_index=1, - is_fiducial=False, - ppm=False, - **kargs - ): - if size is None: - size = [20, 20] - if pos is None: - pos = [0, 0] - self._size = size - self._shape = None - self.parent = parent - self._model_row_index = model_row_index - self._is_fiducial = is_fiducial - ROI.__init__(self, pos, size, **kargs) - - self._beam_position = parent.get_beam_mark_on_camera_xy() - self.set_gonio_xy(*gonio_xy) - - self.setToolTip(f"{'fiducial' if is_fiducial else 'crystal'} {model_row_index}") - - # parent.pixelsPerMillimeter.connect(self.update_pixels_per_millimeter) - # parent.beamCameraCoordinatesChanged.connect(self.update_beam_position) - # parent.fast_x_position.connect(self.follow_x) - # parent.fast_y_position.connect(self.follow_y) - self._ppm = ppm - self._original_ppm = ppm # marker only works at original zoom - # because zoom axis is not aligned - # with viewing axis - - # self.calculate_gonio_xy() - self.sigRegionChanged.connect(self.invalidate) - # self.sigRegionChangeFinished.connect(self.calculate_gonio_xy) - # self.sigHoverEvent.connect(self.my_hovering) - self.aspectLocked = False - self.translatable = True - self.rotateAllowed = False - self.invertible = False - self.deletable = False - - def enable_stage_tracking(self): - self.parent.fast_x_position.connect(self.follow_x) - self.parent.fast_y_position.connect(self.follow_y) - - def disable_stage_tracking(self): - self.parent.fast_x_position.disconnect(self.follow_x) - self.parent.fast_y_position.disconnect(self.follow_y) - - def follow_x(self, v): - self.follow_stage(fx=v) - - def follow_y(self, v): - self.follow_stage(fy=v) - - def my_hovering(self, event): - print("hovering: {}".format(self.mouseHovering)) - - def get_model_row(self): - return self._model_row_index - - def update_ppm(self, ppm): - self._ppm = ppm - self.recalc_camera_position() - - def follow_stage(self, fx=None, fy=None): - self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY() - self.recalc_camera_position() - - def set_gonio_xy(self, x, y): - self._gx, self._gy = x, y - - def disconnect_signals(self): - self.parent.pixelsPerMillimeter.disconnect(self.update_ppm) - self.parent.beamCameraCoordinatesChanged.disconnect(self.update_beam_position) - self.parent.fast_x_position.disconnect(self.follow_x) - self.parent.fast_y_position.disconnect(self.follow_y) - - def reconnect_signals(self): - self.parent.pixelsPerMillimeter.connect(self.update_ppm) - self.parent.beamCameraCoordinatesChanged.connect(self.update_beam_position) - self.parent.fast_x_position.connect(self.follow_x) - self.parent.fast_y_position.connect(self.follow_y) - - def removeClicked(self): - self.disconnect_signals() - super(CrystalCircle, self).removeClicked() - - def recalc_camera_position(self): - idx = self._model_row_index - ppm = self._ppm - bx, by = self.parent.get_beam_mark_on_camera_xy() - gx, gy = self._gx, self._gy # marker position in gonio coords - fx, fy = self._fx, self._fy - # distance to beam in pixels for new ppm - dx, dy = (ppm * (fx - gx), ppm * (fy - gy)) - x, y = bx - dx, by + dy - # logger.debug(f"recalc {idx}: cam = {(x,y)}, gonio = {(gx, gy)}") - self.setPos(x, y) - - def update_beam_position(self, pos): - self._beam_position = pos - - def show_size(self): - try: - ppm = self._ppm - except: - return - w, h = self.getState()["size"] - # convert to micrometer - nw, nh = (w * 1000) / ppm, (h * 1000) / ppm - - def invalidate(self): - self._shape = None - self.prepareGeometryChange() - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - if self._shape is None: - radius = self.getState()["size"][1] - p = QtGui.QPainterPath() - p.moveTo(Point(0, -radius)) - p.lineTo(Point(0, radius)) - p.moveTo(Point(-radius, 0)) - p.lineTo(Point(radius, 0)) - stroker = QtGui.QPainterPathStroker() - stroker.setWidth(10) - outline = stroker.createStroke(p) - self._shape = outline - - return self._shape - - def paint(self, p, *args): - if abs(self._ppm - self._original_ppm) < 1.: - if self._is_fiducial: - pen = mkPen(color="r", width=2) - else: - pen = mkPen(color="y", width=2) - else: - pen = mkPen(color="#ffffaa88", width=2) - x, y = self.getState()["size"] - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(pen) - - p.drawLine(Point(0, -x), Point(0, x)) - p.drawLine(Point(-x, 0), Point(x, 0)) - p.drawEllipse(-x, -y, 2 * x, 2 * y) - - -class Crosshair(ROI): - """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" - - def __init__(self, pos=None, size=None, **kargs): - if size is None: - size = [10, 10] - if pos is None: - pos = [0, 0] - self._shape = None - ROI.__init__(self, pos, size, **kargs) - - self.sigRegionChanged.connect(self.invalidate) - self.aspectLocked = True - - def invalidate(self): - self._shape = None - self.prepareGeometryChange() - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - if self._shape is None: - radius = self.getState()["size"][1] - p = QtGui.QPainterPath() - p.moveTo(Point(0, -radius)) - p.lineTo(Point(0, radius)) - p.moveTo(Point(-radius, 0)) - p.lineTo(Point(radius, 0)) - # p = self.mapToDevice(p) - stroker = QtGui.QPainterPathStroker() - stroker.setWidth(10) - outline = stroker.createStroke(p) - # self._shape = self.mapFromDevice(outline) - self._shape = outline - - return self._shape - - def paint(self, p, *args): - radius = self.getState()["size"][1] - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - - p.drawLine(Point(0, -radius), Point(0, radius)) - p.drawLine(Point(-radius, 0), Point(radius, 0)) diff --git a/GenericDialog.py b/GenericDialog.py deleted file mode 100644 index cafb121..0000000 --- a/GenericDialog.py +++ /dev/null @@ -1,204 +0,0 @@ -import logging -_log = logging.getLogger(__name__) - -import re -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QLineEdit, QTextEdit, QGridLayout, QVBoxLayout, QWidget, QDialog, QDialogButtonBox, QPushButton -from PyQt5.QtWidgets import QDoubleSpinBox, QSpinBox, QCheckBox, QLabel - -def Spinner(value, min=None, max=None, decimals=None, single_step=None, prefix=None, suffix=None): - if type(value) is float: - s = QDoubleSpinBox() - else: - s = QSpinBox() - - if min is not None: - s.setMinimum(min) - if max is not None: - s.setMaximum(max) - try: - if decimals is not None: - s.setDecimals(decimals) - except: - pass - if single_step is not None: - s.setSingleStep(single_step) - if prefix is not None: - s.setPrefix(prefix) - if suffix is not None: - s.setSuffix(suffix) - return s - -def Checkbox(value: bool, label: str): - box = QCheckBox(label) - box.setChecked(value) - return box - -class MagicLabel(QLabel): - entered = pyqtSignal(str) - left = pyqtSignal() - - def __init__(self, label, help_str, help_buddy=None): - super(MagicLabel, self).__init__(label) - self._help_buddy = help_buddy - self._help_str = help_str - - def enterEvent(self, event): - print(self._help_str) - if self._help_buddy: - print(f"yay!: {self._help_str}") - else: - super().enterEvent(event) - - def leaveEvent(self, event): - super().leaveEvent(event) - - -class GenericDialog(QDialog): - def __init__(self, parent=None, settings=None, title=None, message=None, inputs={}, use_buttons=True): - super(GenericDialog, self).__init__() - - self.settings = settings - - # self.setModal(True) - self.setAccessibleName("genericDialog") - self.setLayout(QVBoxLayout()) - - - bbox = QDialogButtonBox() - doneButton = QPushButton("Done") - doneButton.clicked.connect(self.accept) - bbox.addButton(doneButton, QDialogButtonBox.AcceptRole) - - if use_buttons: - undoButton = QPushButton("Undo") - undoButton.clicked.connect(self.undo_all) - - discardButton = QPushButton("Discard") - discardButton.clicked.connect(self.discard) - - bbox.addButton(undoButton, QDialogButtonBox.ActionRole) - bbox.addButton(discardButton, QDialogButtonBox.RejectRole) - - lb = QLabel(title) - lb.setAccessibleName("genericDialogTitle") - lb.setAlignment(Qt.AlignHCenter) - self.layout().addWidget(lb) - - gbox = QWidget() - self.layout().addWidget(gbox) - gbox.setLayout(QVBoxLayout()) - mlabel = QLabel(message) - mlabel.setAccessibleName("genericDialogMessage") - - gbox.layout().addWidget(mlabel) - self.setWindowTitle(title) - - self.contents = QWidget() - self.contents.setAccessibleName("genericDialogContents") - self.layout().addWidget(self.contents) - self.layout().addStretch() - self.layout().addWidget(bbox) - if not inputs: - inputs = {'test': ('Text test', 'initial', QLineEdit()), - 'float': ('Text test 2', 3.14, QDoubleSpinBox(), 5.0), - 'integer': ('Text test 2', 42, QSpinBox()), - } - - self.contents.setLayout(QGridLayout()) - layout = self.contents.layout() - layout.setColumnStretch(1, 2) - self.results = {} - self._undo = {} - for row, config in enumerate(inputs.items()): - key, config = config - config = list(config) - if len(config) == 3: - config.extend([""]) - label, value, widget, help_str = config - labwig = MagicLabel(label, help_str) - layout.addWidget(labwig, row, 0) - layout.addWidget(widget, row, 1) - - if hasattr(widget, 'setChecked'): - widget.setChecked(value) - widget.stateChanged.connect(lambda v, k=key, w=widget.isChecked: self.update_results(k, w)) - - elif hasattr(widget, 'setValue'): - widget.setValue(value) - widget.valueChanged.connect(lambda v, k=key, w=widget.value: self.update_results(k, w)) - - elif hasattr(widget, 'setPlainText'): - widget.setPlainText(value) - widget.textChanged.connect(lambda k=key, w=widget.toPlainText: self.update_results(k, w)) - - elif hasattr(widget, 'setText'): - widget.setText(value) - widget.editingFinished.connect(lambda k=key, w=widget.text: self.update_results(k, w)) - - self.results[key] = value - self._undo[key] = (widget, value) - - def undo_all(self): - for k, uinfo in self._undo.items(): - widget, value = uinfo - if hasattr(widget, 'setChecked'): - widget.setChecked(value) - - elif hasattr(widget, 'setValue'): - widget.setValue(value) - - elif hasattr(widget, 'setPlainText'): - widget.setPlainText(value) - - elif hasattr(widget, 'setText'): - widget.setText(value) - - def discard(self): - self.undo_all() - self.reject() - - def set_value(self, widget, url): - m = re.match("^(.*?)://(.*)", url, re.MULTILINE|re.DOTALL) - method, value = m.groups() - if method == "setValue": - widget.setValue(float(value)) - elif method == "setText": - widget.setText(value) - else: - widget.setPlainText(value) - if self.settings is not None: - self.settings.setValue(k, value) - - def update_results(self, key, get_value): - value = get_value() - _log.debug(f"settigns {key} => {value}") - self.results[key] = value - if self.settings is not None: - self.settings.setValue(k, value) - - def keyPressEvent(self, key_event): - if key_event != Qt.Key_Escape: - super().keyPressEvent(key_event) - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - - app = QApplication(sys.argv) - title = 'A Lovely Title' - message = 'thisn sohudl explain what do tod ow with this dialog and in fact we should make sure that it will be wrapping aorufnnfb' - main = GenericDialog(title=title, message=message, inputs={ - 'bananas': ('Number of Bananas', 2, QSpinBox(), 10), - 'apples': ('Number of Apples', 5.5, QDoubleSpinBox(), 23), - 'really': ('Indeed?', True, Checkbox(True, "Contr")), - 'words': ('Words', 'words go here', QLineEdit(), "a few words"), - 'texts': ('Words', 'big words go here', QTextEdit(), """quite a drama\n to write here, really something awfull long"""), - }) - if main.exec(): - print(main.results) - for k, v in main.results.items(): - print('{} {} = {}'.format(k, type(v), v)) - sys.exit(app.exec_()) - diff --git a/ModuleFixTarget.py b/ModuleFixTarget.py index 45869e7..08bc213 100644 --- a/ModuleFixTarget.py +++ b/ModuleFixTarget.py @@ -195,63 +195,20 @@ class WndFixTarget(QWidget): #btnDelAll.clicked.connect(lambda x: _log.warning("TODO: IMPLEMENT") ) bl.addWidget(btnFit, 2, 1,1,1) - - - - # but = QPushButton("Dump to console") - # but.clicked.connect(self.dump_data) - # bl.addWidget(but, 10, 0, 1, 1) - - but = QCheckBox("collect fiducials") - but.setChecked(False) - but.setToolTip("Collect or not the fiducial positions.") - self._collect_fiducials = False - but.stateChanged.connect(self.set_collect_fiducials) - bl.addWidget(but, 10, 0, 1, 1) - - - but = QCheckBox("draw crystal marks") - but.setChecked(False) - self._draw_crystal_marks = False - but.stateChanged.connect(self.set_draw_crystal_marks) - bl.addWidget(but, 10, 1, 1, 1) - - - but = QPushButton("Transform") - but.clicked.connect(self.transform_non_fiducials_in_model) - bl.addWidget(but, 20, 0, 2, 2) - layout.addWidget(frame) - self.markersTable = QTableWidget() - self.markersTable.setSelectionMode(QAbstractItemView.SingleSelection) - self.markersTable.setSelectionBehavior(QAbstractItemView.SelectRows) - self.markersTable.setItemDelegate(MarkerDelegate(self)) + #self.markersTable.itemSelectionChanged.connect(self.selection_changed) + #self.markersTable.setContextMenuPolicy(Qt.ActionsContextMenu) - # self.markersTable.horizontalHeader().setDefaultSectionSize(80) - self.markersTable.setColumnCount(5) - self.markersTable.setHorizontalHeaderLabels(("Fiducial?", "orig X", "orig Y", "X", "Y")) - hh=self.markersTable.horizontalHeader() - hh.setSectionResizeMode(0, QHeaderView.ResizeToContents) - hh.setSectionResizeMode(1, QHeaderView.ResizeToContents) - hh.setSectionResizeMode(2, QHeaderView.ResizeToContents) - hh.setSectionResizeMode(3, QHeaderView.ResizeToContents) - hh.setSectionResizeMode(4, QHeaderView.ResizeToContents) - hh.setSectionResizeMode(QHeaderView.Stretch) + #deleteRowAction = QAction("Delete this row", self) + #deleteRowAction.triggered.connect(self.delete_selected) + #self.markersTable.addAction(deleteRowAction) - self.markersTable.itemSelectionChanged.connect(self.selection_changed) + #moveRequestAction = QAction("Move Here", self) + #moveRequestAction.triggered.connect(self.request_stage_movement) + #self.markersTable.addAction(moveRequestAction) - self.markersTable.setContextMenuPolicy(Qt.ActionsContextMenu) - - deleteRowAction = QAction("Delete this row", self) - deleteRowAction.triggered.connect(self.delete_selected) - self.markersTable.addAction(deleteRowAction) - - moveRequestAction = QAction("Move Here", self) - moveRequestAction.triggered.connect(self.request_stage_movement) - self.markersTable.addAction(moveRequestAction) - - layout.addWidget(self.markersTable, stretch=2) - self.connect_all_signals() + #layout.addWidget(self.markersTable, stretch=2) + #self.connect_all_signals() #self._yamlFn=fn=os.path.expanduser('~/.config/PSI/SwissMX.yaml') @@ -259,15 +216,74 @@ class WndFixTarget(QWidget): self._data=data self._tree=tree=pg.DataTreeWidget(data=data) layout.addWidget(tree, stretch=2) + #Context menu: by default the function + # def contextMenuEvent(self, event) is called... but this requests to overload the function + # and to build an own context menu + # much simpler is to use Qt.ActionsContextMenu + # and to handle the context in the parent plass + + tree.setContextMenuPolicy(Qt.ActionsContextMenu) + + act = QAction("delete", self) + act.triggered.connect(self.tree_ctx_delete) + tree.addAction(act) + + act = QAction("center in view", self) + act.triggered.connect(self.tree_ctx_center) + tree.addAction(act) + + #contextMenuEvent + def tree_get_path(self): + path=[] + it=self._tree.currentItem() + while it: + d=it.data(0,0) + try: + d=int(d) + except ValueError: + pass + if d!= '': + path.append(d) + it=it.parent() + return path + + def tree_ctx_delete(self): + app=QApplication.instance() + path=self.tree_get_path() + if len(path)==1: + try: + wnd=app._mainWnd + except AttributeError: + _log.info('_mainWnd not handeled') + else: + vb=wnd.vb + grp=wnd._goTracked + go=grp.childItems()[path[0]] + vb.removeItem(go) + data=grp.childItems() + if not len(data): + grp.setFlag(grp.ItemHasNoContents) + self._tree.setData(data) + + def tree_ctx_center(self): + app=QApplication.instance() + path=self.tree_get_path() + if len(path)==1: + try: + wnd=app._mainWnd + except AttributeError: + _log.info('_mainWnd not handeled') + else: + vb=wnd.vb + grp=wnd._goTracked + go=grp.childItems()[path[0]] + vb.autoRange(items=(go,)) + #r1=vb.viewRect() + #r2=vb.itemBoundingRect(go) + #if not r1.intersects(r2): def load_file(self, filename=None): app = QApplication.instance() - #cfg = app._cfg - #logger = logging.getLogger("preloc.loadMarkers") - #def_folder = join(folders.pgroup_folder, "preloc_sheets") #ZAC: orig. code - #def_folder ='' - #data_folder = cfg.value("folders/last_prelocation_folder", def_folder) - data_folder='' if filename is None: filename, _ = QFileDialog.getOpenFileName(self,"Load data file",None, "json files (*.json);;all files (*)",) @@ -282,14 +298,7 @@ class WndFixTarget(QWidget): data=json.load(f,object_hook=MyJsonDecoder) else: raise(IOError('unsupported file type')) - #elif ext=='yaml': - # with open(filename, 'r') as f: - # data = yaml.load(f) - #elif ext=='pkl': - # with open(filename, 'rb') as f: - # data=pickle.load(f) - #self._data=data self._tree.setData(data) try: wnd=app._mainWnd @@ -399,6 +408,14 @@ class WndFixTarget(QWidget): # pickle.dump(self._data, f) #print(self._data) + + def dump_data(self): + print(self._data) + #for ref, x, y, cx, cy, ox, oy in self.get_data(): + # print(f"fiducial:{ref} [{x}, {y}, {cx}, {cy}]") + + # **********3 OBSOLETE + def delete_selected(self): row = self._current_row try: @@ -418,337 +435,6 @@ class WndFixTarget(QWidget): self.selectedRowChanged.emit(row) _log.debug("selection changed: current row {}".format(row)) - def connect_all_signals(self): - self.prefixSelected.connect(lambda t: self._label_prefix.setText(t)) - self.dataFileLoaded.connect(lambda t: self._label_datafile.setText(t)) - - def markerItemChanged(self, item): - print(item.row(), item.column()) - - def set_fiducial_coords(self, camx, camy, gx, gy): - tbl = self.markersTable - row = self._current_row - try: - row += 0 - except: - _log.warning("select a row first") - QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",) - return - - origx = tbl.item(row, ORIGINAL_X).data(RoleOriginalCoord_X) - origy = tbl.item(row, ORIGINAL_Y).data(RoleOriginalCoord_Y) - - item = tbl.item(row, DATA_ITEM) - item.setData(RoleCameraCoord_X, camx) - item.setData(RoleCameraCoord_Y, camy) - item.setData(RoleGoniometerCoord_X, gx) - item.setData(RoleGoniometerCoord_Y, gy) - # item.setData(RoleOriginalCoord_X, origx) - # item.setData(RoleOriginalCoord_Y, origy) - # logger.debug(': [{}] = Original: {}, {} | Camera: {}, {} | Gonio: {}, {}'.format(1+row, origx, origy, camx, camy, gx, gy)) - logger.debug(f": [{1+row}] = Original: {origx}, {origy} | Camera: {camx}, {camy} | Gonio: {gx}, {gy}") - - tbl.item(row, GONIO_X).setData(Qt.DisplayRole, f"{gx:8.5f} mm\n{camx:8.1f} px") - tbl.item(row, GONIO_Y).setData(Qt.DisplayRole, f"{gy:8.5f} mm\n{camy:8.1f} px") - - # mark row as a fiducial - tbl.item(row, 0).setCheckState(Qt.Checked) - tbl.item(row, 0).setData(Qt.UserRole, True) - tbl.selectRow(row + 1) - self.prelocatedDataUpdated.emit() - - def set_selected_gonio_coords(self, xy): - tbl = self.markersTable - row = self._current_row - try: - row += 0 - except: - _log.warning("select a row first") - QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",) - return - - for n, v in enumerate(xy): - idx = 3 + n - tbl.setCurrentCell(row, idx) # gonio X - _log.debug("item: [{},{}] = {}".format(row, idx, v)) - item = tbl.currentItem() - item.setData(Qt.EditRole, "{:.3f}".format(v)) - item.setData(Qt.UserRole, v) - - # mark row as a fiducial - tbl.setCurrentCell(row, 0) - tbl.currentItem().setCheckState(Qt.Checked) - tbl.currentItem().setData(Qt.UserRole, True) - - def set_data_goniometer(self, row: int, xy): - tbl = self.markersTable - - row = self._current_row - try: - row += 0 - except: - _log.warning("select a row first") - QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",) - return - - for n, v in enumerate(xy): - idx = 3 + n - tbl.setCurrentCell(row, idx) # gonio X - item = tbl.currentItem() - item.setData(Qt.EditRole, "{:.3f}".format(v)) - item.setData(Qt.UserRole, v) - - @pyqtSlot(int) - def set_draw_crystal_marks(self, val): - logger.info(f"{'' if val else 'not '}drawing crystal markers") - self._draw_crystal_marks = val - - @pyqtSlot(int) - def set_collect_fiducials(self, val): - logger.info(f"{'' if val else 'not '}collecting fiducials") - self._collect_fiducials = val - - def get_collection_targets(self): - return self.get_data(fiducials=self._collect_fiducials) - - - def get_data(self, fiducials=True, crystals=True, as_numpy=False): - """return a list of tuples with all defined data - - [ - (is_fiducial(boolean), x, y, gonio_x, gonio_y),... - ] - """ - data = [] - item = self.markersTable.item - - for row in range(self.markersTable.rowCount()): - is_fiducial = Qt.Checked == item(row, 0).checkState() - ditem = item(row, DATA_ITEM) - - x = ditem.data(RoleGoniometerCoord_X) - y = ditem.data(RoleGoniometerCoord_Y) - cx = ditem.data(RoleCameraCoord_X) - cy = ditem.data(RoleCameraCoord_Y) - origx = ditem.data(RoleOriginalCoord_X) - origy = ditem.data(RoleOriginalCoord_Y) - - if is_fiducial and fiducials: - data.append([is_fiducial, x, y, cx, cy, origx, origy]) - if not is_fiducial and crystals: - data.append([is_fiducial, x, y, cx, cy, origx, origy]) - - if as_numpy: - data = np.asarray(data) - return data - - def get_original_coordinates(self, fiducials=True): - """return a numpy array with the original prelocated coordinates of the fiducial entries""" - data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True) - data = data[:, 5:] - zeros = np.zeros(data.shape) - zeros[:, 1] = 1 - return np.concatenate((data, zeros), axis=1) - - def get_goniometer_coordinates(self, fiducials=True): - """return a numpy array with the goniometer coordinates of the fiducial entries""" - data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True) - data = data[:, 1:3] - zeros = np.zeros(data.shape) - zeros[:, 1] = 1 - return np.concatenate((data, zeros), axis=1) - - def get_camera_coordinates(self, fiducials=True): - """return a numpy array with the goniometer coordinates of the fiducial entries""" - data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True) - data = data[:, 3:5] - zeros = np.zeros(data.shape) - zeros[:, 1] = 1 - return np.concatenate((data, zeros), axis=1) - - def dump_matrix(self, M): - scale, shear, angles, translate, perspective = tfs.decompose_matrix(M) - angles_deg = [a * 180 / np.pi for a in angles] - - print("Transformation matrix Aerotech => SwissMX") - print(M) - print((" scale {:9.4f} {:9.4f} {:9.4f}".format(*scale))) - print((" shear {:9.4f} {:9.4f} {:9.4f}".format(*shear))) - print((" angles rad {:9.4f} {:9.4f} {:9.4f}".format(*angles))) - print((" angles deg {:9.4f} {:9.4f} {:9.4f}".format(*angles_deg))) - print((" translate {:9.4f} {:9.4f} {:9.4f}".format(*translate))) - print(("perspective {:9.4f} {:9.4f} {:9.4f}".format(*perspective))) - - def transform_non_fiducials_in_model(self): - forg = self.get_original_coordinates(fiducials=True) - fgon = self.get_goniometer_coordinates(fiducials=True) - fcam = self.get_camera_coordinates(fiducials=True) - - gmat = sort_cmass_3d(fgon) - omat = sort_cmass_3d(forg) - - try: - M_org2gon = tfs.superimposition_matrix(omat.T, gmat.T, scale=True) - M_org2cam = tfs.superimposition_matrix(forg.T, fcam.T, scale=True) - except: - QMessageBox.warning(self.parent(), title="failed to find superimposition matrix", text="Failed to find superimposition matrix.\n\tPlease try again.") - return - - # scale, shear, angles, translate, perspective = tfs.decompose_matrix(M_org2gon) - - org_data = self.get_original_coordinates(fiducials=False) - - gon_data = np.dot(M_org2gon, org_data.T) - cam_data = np.dot(M_org2cam, org_data.T) - - tbl = self.markersTable - num_fiducials = forg.shape[0] - - gon_data = (gon_data.T)[:, 0:2] # only X,Y matters - cam_data = (cam_data.T)[:, 0:2] # only X,Y matters - combined = np.concatenate((gon_data, cam_data), axis=1) - - item = self.markersTable.item # function alias - for row, data in enumerate( - combined, num_fiducials - ): # enumeration starts at *num_fiducials* - gx, gy, cx, cy = data - ditem = item(row, DATA_ITEM) - ditem.setData(RoleCameraCoord_X, cx) - ditem.setData(RoleCameraCoord_Y, cy) - ditem.setData(RoleGoniometerCoord_X, gx) - ditem.setData(RoleGoniometerCoord_Y, gy) - item(row, GONIO_X).setData(Qt.DisplayRole, f"{gx:8.5f} mm\n{cx:8.1f} px") - item(row, GONIO_Y).setData(Qt.DisplayRole, f"{gy:8.5f} mm\n{cy:8.1f} px") - self._xtals_transformed = True - self.prelocatedDataUpdated.emit() - - def request_stage_movement(self): - logger = logging.getLogger("preloc.move_stage") - row = self._current_row - x = self.markersTable.item(row, DATA_ITEM).data(RoleGoniometerCoord_X) - y = self.markersTable.item(row, DATA_ITEM).data(RoleGoniometerCoord_Y) - logger.info(f"request move gonio to {x:.3f}, {y:.3f} mm") - self.moveFastStageRequest.emit(x, y) - - def dump_numpy(self): - R = tfs.random_rotation_matrix(np.random.random(3)) - d = self.get_goniometer_coordinates() - print("dumping") - print(d) - dR = np.dot(R, d) - print(dR.T) - - def get_fiducials(self, as_numpy=False): - return self.get_data(crystals=False, as_numpy=as_numpy) - - def get_crystals(self, as_numpy=False): - return self.get_data(fiducials=False, as_numpy=as_numpy) - - def dump_data(self): - print(self._data) - #for ref, x, y, cx, cy, ox, oy in self.get_data(): - # print(f"fiducial:{ref} [{x}, {y}, {cx}, {cy}]") - - def append_data(self, data): - """append data (a list of values) to the model - - len(data) == 2 => (X, Y) from prelocated coordinate - len(data) == 4 => (X, Y, GX, GY) plus gonio coordinates - len(data) == 5 => (fiducial?, X, Y, GX, GY) plus fiducial - """ - row = self.markersTable.rowCount() - self.markersTable.setRowCount(row + 1) - - data = list(data) - if len(data) == 2: - data.extend([0, 0]) - if len(data) == 4: - data.insert(0, False) # fiducial flag - self.addSingleMarker(row, data) - self.prelocatedDataUpdated.emit() - - def clearMarkers(self): - self.markersTable.setRowCount(0) - self.markersDeleted.emit() - - def generate_random_data(self): - import io - - data = io.StringIO() - npoints = self._num_random_points.value() - for n in range(npoints): - x, y, a, b = ( - np.random.randint(0, 2000), - np.random.randint(0, 2000), - np.random.randint(-4000, 4000) / 1000., - np.random.randint(-4000, 4000) / 1000., - ) - data.write("{}\t{}\t{}\t{}\n".format(x, y, a, b)) - data.seek(0) - data.name = "random.csv" - self.loadMarkers(data) - - - - - def addSingleMarker(self, row, marker): - is_fiducial, origx, origy, gx, gy = marker - _log.debug(f": [{1+row}] | Original: {origx}, {origy} | Gonio: {gx}, {gy}") - item0 = QTableWidgetItem() - item0.setData(Qt.UserRole, is_fiducial) - item0.setFlags(item0.flags() & ~Qt.ItemIsEditable) - item0.setCheckState(Qt.Checked if is_fiducial else Qt.Unchecked) - item0.setTextAlignment(Qt.AlignCenter) - - item1 = QTableWidgetItem("{:.1f}".format(origx)) - item1.setTextAlignment(Qt.AlignRight) - - item2 = QTableWidgetItem("{:.1f}".format(origy)) - item2.setTextAlignment(Qt.AlignRight) - - item3 = QTableWidgetItem("{:.3f} mm".format(gx)) - item3.setFlags(item3.flags() & ~Qt.ItemIsEditable) - item3.setTextAlignment(Qt.AlignRight) - - item4 = QTableWidgetItem("{:.3f} mm".format(gy)) - item4.setFlags(item4.flags() & ~Qt.ItemIsEditable) - item4.setTextAlignment(Qt.AlignRight) - - self.markersTable.setItem(row, 0, item0) - self.markersTable.setItem(row, ORIGINAL_X, item1) - self.markersTable.setItem(row, ORIGINAL_Y, item2) - self.markersTable.setItem(row, GONIO_X, item3) - self.markersTable.setItem(row, GONIO_Y, item4) - self.markerAdded.emit(is_fiducial, [origx, origy, gx, gy]) - - item = self.markersTable.item(row, DATA_ITEM) - item.setData(RoleCameraCoord_X, origx) # initially set to original - item.setData(RoleCameraCoord_Y, origy) # initially set to original - item.setData(RoleGoniometerCoord_X, gx) - item.setData(RoleGoniometerCoord_Y, gy) - item.setData(RoleOriginalCoord_X, origx) - item.setData(RoleOriginalCoord_Y, origy) - -""" -Signals QTableWidget -void cellActivated(int row, int column) -void cellChanged(int row, int column) -void cellClicked(int row, int column) -void cellDoubleClicked(int row, int column) -void cellEntered(int row, int column) -void cellPressed(int row, int column) -void currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) -void currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) -void itemActivated(QTableWidgetItem *item) -void itemChanged(QTableWidgetItem *item) -void itemClicked(QTableWidgetItem *item) -void itemDoubleClicked(QTableWidgetItem *item) -void itemEntered(QTableWidgetItem *item) -void itemPressed(QTableWidgetItem *item) -void itemSelectionChanged() -""" - if __name__ == "__main__": import sys diff --git a/app_config.py b/app_config.py index 02d2cf7..701e08f 100644 --- a/app_config.py +++ b/app_config.py @@ -12,13 +12,9 @@ _log = logging.getLogger(__name__) from PyQt5 import QtCore, QtGui from PyQt5.QtCore import QSettings -from PyQt5.QtWidgets import QApplication, QMainWindow #QLineEdit, QWidget, QGridLayout, QLabel +from PyQt5.QtWidgets import QApplication, QMainWindow import json -import pyqtgraph as pg import numpy as np -import GenericDialog - -#from pyqtgraph.Qt import QtCore, QtGui class MyJsonEncoder(json.JSONEncoder): """ Special json encoder for numpy types """ @@ -60,26 +56,20 @@ class AppCfg(QSettings): DT_MISC="deltatau/miscellaneous" # ---------- OBSOLETE ??? ---------- #ZOOM_BUTTONS="sample_viewing/zoom_buttons" - - SKIP_ESCAPE_TRANSITIONS_IF_SAFE="escape/skip_transitions_if_safe" - - CRYOJET_MOTION_ENABLED="cryojet/motion_enabled" - CRYOJET_NOZZLE_OUT="cryojet/nozzle_out" - CRYOJET_NOZZLE_IN="cryojet/nozzle_in" - + #SKIP_ESCAPE_TRANSITIONS_IF_SAFE="escape/skip_transitions_if_safe" + #CRYOJET_MOTION_ENABLED="cryojet/motion_enabled" + #CRYOJET_NOZZLE_OUT="cryojet/nozzle_out" + #CRYOJET_NOZZLE_IN="cryojet/nozzle_in" #DELTATAU_HOST="deltatau/host" #DELTATAU_SHOW_PLOTS="deltatau/show_plots" - DELTATAU_OMEGACOS="deltatau/omegacos" - DELTATAU_SORT_POINTS="deltatau/sort_points" + #DELTATAU_OMEGACOS="deltatau/omegacos" + #DELTATAU_SORT_POINTS="deltatau/sort_points" #DELTATAU_VELOCITY_SCALE="deltatau/velocity_scale" - - CAMERA_TRANSFORMATIONS="camera/transformations" - CAMERA_ZOOM_TO_PPM="camera/zoom_to_ppm" - - EXPERIMENT_PGROUP="experiment/pgroup" - EXPERIMENT_UID="experiment/uid" - - ACTIVATE_PULSE_PICKER="scanning/activate_pulse_picker" + #CAMERA_TRANSFORMATIONS="camera/transformations" + #CAMERA_ZOOM_TO_PPM="camera/zoom_to_ppm" + #EXPERIMENT_PGROUP="experiment/pgroup" + #EXPERIMENT_UID="experiment/uid" + #ACTIVATE_PULSE_PICKER="scanning/activate_pulse_picker" def __init__(self): super(AppCfg, self).__init__("PSI", "SwissMX") @@ -220,6 +210,8 @@ from pyqtgraph.parametertree import Parameter, ParameterTree class WndParameter(QMainWindow): def __init__(self, parent=None): super(WndParameter, self).__init__(parent) + + self.setWindowTitle('SwissMX Preferences') app=QApplication.instance() cfg=app._cfg #GBL_FLD_SCR_SHOT="global/folder_screenshot" diff --git a/deltatau.py b/deltatau.py index b35f1dd..bdb42a0 100644 --- a/deltatau.py +++ b/deltatau.py @@ -35,7 +35,7 @@ class Deltatau: try: self._comm=comm=PPComm(**param,timeout=2.0) self._gather=gather=Gather(comm) - except socket.timeout as e: + except (socket.timeout,socket.gaierror) as e: _log.critical(f'can not connect to deltatau:"{host}" -> {e}') self._comm=comm=None self._gather=gather=None diff --git a/swissmx.py b/swissmx.py index 6062fd6..1d0b0f4 100755 --- a/swissmx.py +++ b/swissmx.py @@ -45,20 +45,10 @@ ts=timestamp() ts.log('Import part 1/8:') import sys, os, time import json, re -import random, signal,threading - +import random, signal, subprocess import matplotlib as mpl mpl.use('Qt5Agg') # needed to avoid blocking of ui ! -#import Wigis #ZAC: orig. code -#import jungfrau_widget #ZAC: orig. code -#import bernina_pulse_picker #ZAC: orig. code -#import storage #ZAC: orig. code -#from zmq_escape import zescape #ZAC: orig. code -#from helicalscan import HelicalScanGui #ZAC: orig. code -# publisher = pubber.Publisher() - - TASK_JUNGFRAU_SETTINGS = "jungfrau_settings" TASK_SETUP_GEOMETRY_CALIB = "geometry_calib" TASK_SETUP_PPM_CALIBRATION_TBOX = "ppm_calibration_tbox" @@ -78,17 +68,12 @@ import ModuleFixTarget import PrelocatedCoordinatesModel # ZAC: orig. code from EmblModule import EmblWidget #ZAC: orig. code from HelicalTable import HelicalTableWidget #ZAC: orig. code -#from Wigis import Spinner, Checkbox #ZAC: orig. code -#from exceptions import * #ZAC: orig. code -#import qoptic #ZAC: orig. code -#import mx_swissfel #ZAC: orig. code -#swissfel = mx_swissfel.SwissFELMachine() #ZAC: orig. code -#from bernina_pulse_picker import pulsePicker #ZAC: orig. code + ts.log('Import part 3/8:') import qtawesome import qutilities from PyQt5 import QtCore, QtGui -from PyQt5.QtCore import Qt, pyqtSlot, QSize, QRegExp, pyqtSignal, QObject, QThread, QRectF +from PyQt5.QtCore import Qt, pyqtSlot, QSize, QRegExp, pyqtSignal, QObject, QThread, QRectF,QT_VERSION_STR from PyQt5.QtGui import QKeySequence, QPixmap, QRegExpValidator, QFont from PyQt5.QtWidgets import ( QAction, QApplication, QDoubleSpinBox, QFileDialog, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, @@ -96,20 +81,11 @@ from PyQt5.QtWidgets import ( QSplashScreen, QTextBrowser, QToolBox, QVBoxLayout, QWidget,) from PyQt5.uic import loadUiType ts.log('Import part 4/8:') -import CustomROI as CstROI import pyqtUsrObj as UsrGO - -#from CustomROI import BeamMark, Grid, CrystalCircle #ZAC: orig. code - -#from GenericDialog import GenericDialog #ZAC: orig. code -#from dialogs.PreferencesDialog import PreferencesDialog #ZAC: orig. code -#from epics_widgets import zoom #ZAC: orig. code from epics_widgets.MotorTweak import MotorTweak from epics_widgets.SmaractMotorTweak import SmaractMotorTweak from epics_widgets.SimMotorTweak import SimMotorTweak ts.log('Import part 5/8:') -#import matplotlib as mpl -#import matplotlib.pyplot as plt import numpy as np np.set_printoptions(suppress=True,linewidth=196) import pyqtgraph as pg @@ -128,47 +104,24 @@ import deltatau import detector ts.log('Import part 8/8:') - -#_URL = "http://PC12288:8080" -# import TellClient -# tell = TellClient.TellClient(_URL) - -#import eventer #ZAC: orig. code -#from detector_control import jungfrau_detector #ZAC: orig. code -#from findxtal import findObj #ZAC: orig. code - -#if simulated: #ZAC: orig. code #ZAC: orig. code -# _log.warning("simulation mode enabled") -# qoptic_zoom = qoptic.FeturaClientBogus() -#else: -# qoptic_zoom = qoptic.FeturaClient() - -#from epics_widgets import MotorTweak - - -#user = getpass.getuser() #ZAC: orig. code -#home = os.path.expanduser("~") -#just_quit = user in ["e10003", "gac-esbmx"] - -#folders = storage.Folders() #ZAC: orig. code - -#from deltatau import DeltaTau, shapepath, helical, DebugPlot #ZAC: orig. code -#delta_tau = DeltaTau() #ZAC: orig. code - -#BROKER_SERVER = "127.0.0.1" #ZAC: orig. code -#BROKER_PORT = 61613 #ZAC: orig. code - -# sample_camera = camera.camera_server(basename="SARES20-PROF142-M1") - -#_log.info(f"connecting to microscope to camera server: {appsconf['microscope']['sample_camera']['pv_prefix']} ") -#sample_camera = camera.camera_server(basename=appsconf["microscope"]["sample_camera"]["pv_prefix"]) #ZAC: orig. code - def tdstamp(): return time.strftime("%Y%m%dH%H%M%S") def datestamp(): return time.strftime("%Y%m%d") +def get_version(path='.'): + #sys.stdout.write('getVersion() -> using git command -> ') + p = subprocess.Popen(f'git -C {path} describe --match ''*.*.*'' --long --tags --dirty', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + retval = p.wait() + res=p.stdout.readline() + p.stdout.close() + #res=res.decode()[1:-1].split('-',2) + res=res.decode()[:-1].split('-',2) + ver='.'.join(res[:2]) + gitcmt=res[2][1:] + return (ver,gitcmt) + def sigint_handler(*args): """Handler for the SIGINT signal.""" app=QApplication.instance() @@ -177,7 +130,6 @@ def sigint_handler(*args): class AcquisitionAbortedException(Exception): pass - class Sequencer(QObject): finished = pyqtSignal() timeoutExpired = pyqtSignal() @@ -260,13 +212,6 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): QtGui.QFontDatabase.addApplicationFont("fonts/Inconsolata-Bold.ttf") QtGui.QFontDatabase.addApplicationFont("fonts/Baloo-Regular.ttf") - # TODO: Cleanup many member functions that are unused or obsolete - 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.create_escape_toolbar() @@ -333,13 +278,13 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.init_actions() #self.prepare_microscope_page() - self.prepare_left_tabs() + self.prepare_wd_tabs_left() #self.update_beam_marker(qoptic_zoom.get_sp()) #ZAC: orig. code - self._centerpiece_stack.setCurrentIndex(0) - self._centerpiece_stack.currentChanged.connect(self.cb_update_center_widget) - self._OLD_init_validators() + self._wd_stack.setCurrentIndex(0) + self._wd_stack.currentChanged.connect(self.cb_update_center_widget) + #self._OLD_init_validators() #self.init_settings_tracker() ? not needed, was for TELL ? - self._OLD_wire_storage() + #self._OLD_wire_storage() self.cb_update_center_widget(0) # start camera updater curzoom = app._zoom.get_val() @@ -364,7 +309,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): sz = [int(s) for s in cfg.value(cfg.WINDOW_SPLITTER)] else: sz=(500, 1200) - self._main_splitter.setSizes(sz) + self._wd_splitter.setSizes(sz) #if AppCfg.BEAM_MARKER_POSITIONS in keys: # self._beam_markers = s.value(AppCfg.BEAM_MARKER_POSITIONS) @@ -523,11 +468,11 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.toolBar.addAction(action) # icon = qtawesome.icon("material.shutter_speed") - action = QAction("Use Pulse Picker", self) - action.setCheckable(True) - action.setChecked(cfg.option(AppCfg.ACTIVATE_PULSE_PICKER)) - action.triggered.connect(lambda: cfg.toggle_option(AppCfg.ACTIVATE_PULSE_PICKER)) - self.toolBar.addAction(action) + #action = QAction("Use Pulse Picker", self) + #action.setCheckable(True) + #action.setChecked(cfg.option(AppCfg.ACTIVATE_PULSE_PICKER)) + #action.triggered.connect(lambda: cfg.toggle_option(AppCfg.ACTIVATE_PULSE_PICKER)) + #self.toolBar.addAction(action) icon = qtawesome.icon("material.timeline") action = QAction(icon, "Add Line", self) @@ -575,9 +520,9 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.toolBar.widgetForAction(action).setAccessibleName("action_DataCollection") self.actionQuit.triggered.connect(self.cb_really_quit) - self.actionPreferences.triggered.connect(self._OLD_openPreferencesDialog) + self.actionPreferences.triggered.connect(self.cb_modify_app_param) self.actionHome_Fast_Stages.triggered.connect(self.cb_deltatau_home_faststages) - self.actionUser_Storage.triggered.connect(self._OLD_update_user_and_storage) + self.actionAbout.triggered.connect(self.cb_about) self.shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_L), self) self.shortcut.activated.connect(self._OLD_roi_add_line) @@ -601,6 +546,12 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.shortcut.activated.connect(self._OLD_camera_pause_toggle) # adding a menu entry to one of the menus + #action = QAction("parameters", self) + #action.setToolTip("modify application parameters") + #action.setStatusTip("modify application parameters") + #action.triggered.connect(self.cb_modify_app_param) + #self.menuAdvanced.addAction(action) + #action = QAction("geometry", self) #action.setToolTip("Update optical center, beam marker size etc.") #action.setStatusTip("Update optical center, beam marker size etc.") @@ -631,14 +582,6 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): #action.triggered.connect(cfg.dlg_deltatau_parameters) #self.menuAdvanced.addAction(action) - action = QAction("parameters", self) - action.setToolTip("modify application parameters") - action.setStatusTip("modify application parameters") - - action.triggered.connect(self.cb_modify_app_param) - - 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") @@ -665,7 +608,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): _log.info('disconnect PV callback') cfg.setValue(cfg.WINDOW_GEOMETRY, self.saveGeometry()) cfg.setValue(cfg.WINDOW_STATE, self.saveState()) - cfg.setValue(cfg.WINDOW_SPLITTER, self._main_splitter.sizes()) + cfg.setValue(cfg.WINDOW_SPLITTER, self._wd_splitter.sizes()) cfg.setValue('last_active', time.time()) _log.info('save settings') #QMainWindow.closeEvent(self, event) @@ -755,19 +698,13 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.setStyleSheet(sheet.read()) def setup_sliders(self): - cont = self._rightmost + cont = self._wd_right 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.init_settings() @@ -1506,46 +1443,44 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): def cb_esc_sample_exchange(self): app=QApplication.instance() - self._esc_state ="busy" steps = [] #if option(CRYOJET_MOTION_ENABLED): # steps.append(lambda: self.move_cryojet_nozzle("out")) - steps.extend( - [ + steps=[ + lambda:self.move_detector("out"), lambda: self.move_post_tube("out"), - lambda: app._backlight.move("out", wait=True), + lambda: app._backlight.move("out"), lambda: self.move_collimator("out"), ] - ) - self.esc_run_steps(steps, "Transitioning to Sample Exchange") - self._esc_state ="ManualSampleExchange" + self.esc_run_steps(steps, "Transitioning to Sample Exchange","ManualSampleExchange") def cb_esc_sample_alignment(self): app=QApplication.instance() - self._esc_state ="busy" steps = [ # lambda: sample_selection.tell.set_current(30.0), - lambda: self.move_collimator("ready") + lambda: self.move_collimator("ready"), + lambda:self.move_detector("out"), + lambda:self.move_post_tube("out"), + lambda:app._backlight.move("in"), + lambda:self.move_collimator("out"), ] #if option(CRYOJET_MOTION_ENABLED): # steps.extend([lambda: self.move_cryojet_nozzle("in")]) - steps.extend([lambda: self.move_post_tube("out"), lambda: app._backlight.move("in")]) - self.esc_run_steps(steps, "Transitioning to Sample Alignment") - self._esc_state ="SampleAlignment" + #steps.extend([lambda: self.move_post_tube("out"), lambda: app._backlight.move("in")]) + self.esc_run_steps(steps, "Transitioning to Sample Alignment","SampleAlignment") def cb_esc_data_collection(self): app=QApplication.instance() - self._esc_state ="busy" steps = [ # lambda: sample_selection.tell.set_current(30.0), lambda: app._backlight.move("out"), lambda: self.move_post_tube("in"), lambda: self.move_collimator("in"), + lambda:self.move_detector("in"), ] #if option(CRYOJET_MOTION_ENABLED): # steps.extend([lambda: self.move_cryojet_nozzle("in")]) - self.esc_run_steps(steps, "Transitioning to Data Collection") - self._esc_state ="DataCollection" + self.esc_run_steps(steps, "Transitioning to Data Collection","DataCollection") def cb_really_quit(self): """called when user Ctrl-Q the app""" @@ -1555,9 +1490,43 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): def cb_deltatau_home_faststages(self): pfx=QApplication.instance()._cfg.value(AppCfg.GBL_DEV_PREFIX)[0] - _log.warning("homing fast stages") + _log.info("homing fast stages") epics.PV(f"{pfx}1:ASYN.AOUT").put(b"enable plc 1") + def cb_about(self): + try: + ver,gitcmt=get_version() + v_SMX=f'{ver} git:{gitcmt}' + except: + v_SMX='git version failed' + try: + ver,gitcmt=get_version('..') + v_SMXT=f'{ver} git:{gitcmt}' + except: + v_SMXT='git version failed' + + txt=f'''About Swissmx: + +SwissMX: {v_SMX} +SwissMXTools: {v_SMXT} + +qt:{QT_VERSION_STR} +pyqtgraph:{pg.__version__} +numpy:{np.__version__} +matplotlib:{mpl.__version__} +epics:{epics.__version__} + +Copyright (c) 2022 by Paul Scherrer Institute +(http://www.psi.ch) + +Based on Zac great first implementation +Author Thierry Zamofing (thierry.zamofing@psi.ch) +''' + + QMessageBox.about(self, "SwissMX", txt) + pass + + def cb_testcode(self): try: tc=self._testCode @@ -1602,8 +1571,8 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): #print(vb.childGroup.childItems()) pass - def prepare_left_tabs(self): - tabs = self._left_tabs + def prepare_wd_tabs_left(self): + tabs = self._wd_tabs_left tabs.currentChanged.connect(self.cb_switch_task) setup_tab = self._tab_setup @@ -1832,8 +1801,8 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): def cb_switch_task(self, index=0): - stack = self._centerpiece_stack - task = self._left_tabs.currentWidget().accessibleName() + stack = self._wd_stack + task = self._wd_tabs_left.currentWidget().accessibleName() setup_task = self._setup_toolbox.currentWidget().accessibleName() method = self._tabs_daq_methods.currentWidget().accessibleName() @@ -1992,7 +1961,6 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): dt_misc = cfg.value(AppCfg.DT_MISC) geo = app._geometry - verbose=0xff fn='/tmp/shapepath' try: dt=app._deltatau @@ -2006,12 +1974,9 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): sp=dt._shapepath sp.verbose=dt_misc['verbose'] sp.points=kwargs['points'] - #sp.gen_grid_points(w=15, h=15, pitch=3, rnd=0, ofs=(0, +2000)) - #sp.gen_grid_points(w=5, h=10, pitch=1, rnd=0, ofs=(0, 0));sp.sort_points(False, 10);sp.points - #sp.sort_points(False, 15); sp.meta['pt2pt_time']=dt_misc['pt2pt_time'] sp.setup_gather() - sp.setup_sync(verbose=verbose&32, timeOfs=0.05) + sp.setup_sync(verbose=sp.verbose&0x20, timeOfs=0.05) try: p=geo._fitPlane sp.setup_coord_trf(cz=f'{p[0]:+.18g}X{p[1]:+.18g}Y{p[2]:+.18g}') # reset to shape path system @@ -2037,7 +2002,8 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): plt.show(block=False) #plt.show(block=True) - def esc_run_steps(self, steps, title): + def esc_run_steps(self, steps, title, esc_state): + self._esc_state ="busy" with pg.ProgressDialog(title, 0, len(steps)) as dlg: for step in steps: step() @@ -2045,8 +2011,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): if dlg.wasCanceled(): QMessageBox.warning(self, "escape steps", "ABORTED" + title) break - - #backlight="{'pos_in': -10.0, 'pos_out': 0.0}" + self._esc_state = esc_state def move_post_tube(self, pos): # post_sample_tube="{'x_up': -0.2, 'y_up': 0.0, 'x_down': 0.0, 'y_down': 0.0, 'x_out_delta': 0.0, 'y_out_delta': 0.0, 'z_in': 0.0, 'z_out': 0.0}" @@ -3554,7 +3519,7 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): self.re_connect_collect_button() def _OLD_create_escape_toolbar(self): - cont = self._rightmost + cont = self._wd_right w = QGroupBox("Escape") layout = QHBoxLayout() w.setLayout(layout) @@ -3725,7 +3690,6 @@ class WndSwissMx(QMainWindow, Ui_MainWindow): if __name__=="__main__": def main(): - from PyQt5.QtCore import QT_VERSION_STR #_log.info(f'Version: pyqtgraph:{pg.__version__} matplotlib:{mpl.__version__} numpy:{np.__version__} epics:{epics.__version__} qt:{QT_VERSION_STR}' ) _log.info(f'Version: pyqtgraph:{pg.__version__} epics:{epics.__version__} qt:{QT_VERSION_STR}' ) import argparse, socket diff --git a/swissmx.ui b/swissmx.ui index 0bd26df..cffa71a 100644 --- a/swissmx.ui +++ b/swissmx.ui @@ -16,7 +16,7 @@ 0 - + 0 @@ -46,7 +46,7 @@ 0 - + 0 @@ -65,7 +65,7 @@ true - + 0 @@ -107,81 +107,13 @@ Data Storage - - - - Prefix - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Folder - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Folder where the data will be stored; triple-click to select. - - - Folder where the data will be stored; triple-click to select. Type 'folder' in a console to go here. - - - TextLabel - - - Qt::PlainText - - - true - - - Qt::TextSelectableByMouse - - - - - - - Project - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + - - - - Filenames - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - + Run @@ -191,46 +123,16 @@ - - - - false - - - true - - - - - - - false - - - true - - - - + - P-group + Prefix Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - pgroup - - - p-group-undefined - - - @@ -239,89 +141,7 @@ Experimental Parameters - - - - - - 0 - 0 - - - - s - - - 3 - - - 0.010000000000000 - - - 1000.000000000000000 - - - 0.100000000000000 - - - 0.100000000000000 - - - - - - - Oscillation Step - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Exposure Time - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Blast radius - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - µm - - - 200.000000000000000 - - - - - - - - 0 - 0 - - - - deg - - - - + @@ -614,16 +434,16 @@ - + - 1 + 0 - + 0 @@ -664,7 +484,6 @@ &Advanced - @@ -718,16 +537,12 @@ - _le_project _le_prefix - _dsb_exposure_time - _dsb_oscillation_step _tabs_daq_methods _sb_grid_y_step - _left_tabs - _label_actual_prefix + _wd_tabs_left _grid_inspect_area - _label_runnumber + _le_runnumber _bt_add_grid