From 16c5c816f4ff6d85cea6f21a5c69c3287eef0dfa Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Sat, 22 Jul 2023 16:59:28 -0400 Subject: [PATCH 001/234] Minimal working version. Needs refactor --- gui/control_main.py | 26 +++++++++----------------- gui/vector.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 gui/vector.py diff --git a/gui/control_main.py b/gui/control_main.py index 78bebe80..0a0f8ceb 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -49,6 +49,7 @@ UserScreenDialog, ) from gui.raster import RasterCell, RasterGroup +from gui.vector import VectorMarker from QPeriodicTable import QPeriodicTable from threads import RaddoseThread, VideoThread @@ -4293,7 +4294,7 @@ def transform_vector_coords(self, prev_coords, current_raw_coords): } def getVectorObject( - self, prevVectorPoint=None, gonioCoords=None, pen=None, brush=None + self, prevVectorPoint=None, gonioCoords=None, pen=None, brush=None, pointName=None ): """Creates and returns a vector start or end point @@ -4321,20 +4322,12 @@ def getVectorObject( markWidth = 10 # TODO: Place vecMarker in such a way that it matches any arbitrary gonioCoords given to this function # currently vecMarker will be placed at the center of the sample cam - vecMarker = self.scene.addEllipse( - self.centerMarker.x() - - (markWidth / 2.0) - - 1 - + self.centerMarkerCharOffsetX, - self.centerMarker.y() - - (markWidth / 2.0) - - 1 - + self.centerMarkerCharOffsetY, - markWidth, - markWidth, - pen, - brush, - ) + marker_x = self.centerMarker.x() - (markWidth / 2.0) - 1 + self.centerMarkerCharOffsetX + marker_y = self.centerMarker.y() - (markWidth / 2.0) - 1 + self.centerMarkerCharOffsetY + + vecMarker = VectorMarker(marker_x, marker_y, markWidth, markWidth, pen=pen, + brush=brush, parent=self, pointName=pointName) + self.scene.addItem(vecMarker) if not gonioCoords: gonioCoords = { "x": self.sampx_pv.get(), @@ -4377,7 +4370,7 @@ def setVectorPointCB(self, pointName): brush = QtGui.QBrush(QtCore.Qt.red) else: brush = QtGui.QBrush(QtCore.Qt.blue) - point = self.getVectorObject(prevVectorPoint=point, brush=brush) + point = self.getVectorObject(prevVectorPoint=point, brush=brush, pointName=pointName) setattr(self, pointName, point) if self.vectorStart and self.vectorEnd: self.drawVector() @@ -4404,7 +4397,6 @@ def drawVector(self): + self.centerMarkerCharOffsetY, pen, ) - self.vecLine.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True) def clearVectorCB(self): if self.vectorStart: diff --git a/gui/vector.py b/gui/vector.py new file mode 100644 index 00000000..2ac20486 --- /dev/null +++ b/gui/vector.py @@ -0,0 +1,44 @@ +from qtpy import QtWidgets, QtGui, QtCore +import daq_utils + +class VectorMarker(QtWidgets.QGraphicsEllipseItem): + def __init__(self, *args, **kwargs): + brush = kwargs.pop('brush', QtGui.QBrush(QtCore.Qt.blue)) + pen = kwargs.pop('pen', QtGui.QPen(QtCore.Qt.blue)) + self.parent = kwargs.pop('parent', None) + self.pointName = kwargs.pop('pointName', None) + super().__init__(*args, **kwargs) + self.setBrush(brush) + self.setPen(pen) + self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable) + self.setFlag(QtWidgets.QGraphicsEllipseItem.ItemSendsGeometryChanges) + + + def itemChange(self, change, value): + if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged: + if self.parent and self.parent.vecLine: + self.parent.scene.removeItem(self.parent.vecLine) + self.parent.drawVector() + return super().itemChange(change, value) + + + def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: + print("New position:", self.pos()) + if self.parent: + micron_x = self.parent.screenXPixels2microns(self.pos().x()) + micron_y = self.parent.screenYPixels2microns(self.pos().y()) + if self.pointName and getattr(self.parent, self.pointName): + omega = self.parent.omegaRBV_pv.get() + point = getattr(self.parent, self.pointName) + gonio_offset_x, gonio_offset_y, gonio_offset_z, omega = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) + gonioCoords = {"x": self.parent.sampx_pv.get() + gonio_offset_x, + "y": self.parent.sampy_pv.get() + gonio_offset_y, + "z": self.parent.sampz_pv.get() + gonio_offset_z, + "omega": omega} + vectorCoords = self.parent.transform_vector_coords( + point["coords"], gonioCoords + ) + point["coords"] = vectorCoords + point["gonioCoords"] = gonioCoords + + return super().mouseReleaseEvent(event) \ No newline at end of file From b1c63f2c9933f6d1b190c5bf310e69815e01f286 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Sun, 23 Jul 2023 18:45:15 -0400 Subject: [PATCH 002/234] Created a vector object class: - Seperates from ControlMain - Majority of ControlMain dependencies removed --- gui/control_main.py | 272 +++++++------------------------------- gui/vector.py | 316 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 344 insertions(+), 244 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 0a0f8ceb..b71029ff 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -49,7 +49,7 @@ UserScreenDialog, ) from gui.raster import RasterCell, RasterGroup -from gui.vector import VectorMarker +from gui.vector import VectorMarker, VectorWidget from QPeriodicTable import QPeriodicTable from threads import RaddoseThread, VideoThread @@ -157,8 +157,6 @@ def __init__(self): self.popupMessage.setModal(False) self.groupName = "skinner" self.scannerType = getBlConfig("scannerType") - self.vectorStart = None - self.vectorEnd = None self.centerMarkerCharSize = 20 self.centerMarkerCharOffsetX = 12 self.centerMarkerCharOffsetY = 18 @@ -206,6 +204,7 @@ def __init__(self): self.raddoseTimer.timeout.connect(self.spawnRaddoseThread) self.createSampleTab() + self.vector_widget = VectorWidget(main_window=self) self.initCallbacks() if self.scannerType != "PI": @@ -824,11 +823,11 @@ def createSampleTab(self): setVectorStartButton = QtWidgets.QPushButton("Vector\nStart") setVectorStartButton.setStyleSheet("background-color: blue") setVectorStartButton.clicked.connect( - lambda: self.setVectorPointCB("vectorStart") + lambda: self.setVectorPointCB("vector_start") ) setVectorEndButton = QtWidgets.QPushButton("Vector\nEnd") setVectorEndButton.setStyleSheet("background-color: red") - setVectorEndButton.clicked.connect(lambda: self.setVectorPointCB("vectorEnd")) + setVectorEndButton.clicked.connect(lambda: self.setVectorPointCB("vector_end")) self.vecLine = None vectorFPPLabel = QtWidgets.QLabel("Number of Wedges") self.vectorFPP_ledit = QtWidgets.QLineEdit("1") @@ -1540,7 +1539,7 @@ def adjustGraphics4ZoomChange(self, fov): self.processSampMove(self.sampx_pv.get(), "x") self.processSampMove(self.sampy_pv.get(), "y") self.processSampMove(self.sampz_pv.get(), "z") - if self.vectorStart != None: + if self.vector_widget.vector_start != None: self.processSampMove(self.sampx_pv.get(), "x") self.processSampMove(self.sampy_pv.get(), "y") self.processSampMove(self.sampz_pv.get(), "z") @@ -1969,55 +1968,8 @@ def processSampMove(self, posRBV, motID): newY = self.calculateNewYCoordPos(startYX, startYY) raster["graphicsItem"].setPos(raster["graphicsItem"].x(), newY) - self.vectorStart = self.updatePoint(self.vectorStart, posRBV, motID) - self.vectorEnd = self.updatePoint(self.vectorEnd, posRBV, motID) - if self.vectorStart != None and self.vectorEnd != None: - self.vecLine.setLine( - self.vectorStart["graphicsitem"].x() - + self.vectorStart["centerCursorX"] - + self.centerMarkerCharOffsetX, - self.vectorStart["graphicsitem"].y() - + self.vectorStart["centerCursorY"] - + self.centerMarkerCharOffsetY, - self.vectorEnd["graphicsitem"].x() - + self.vectorStart["centerCursorX"] - + self.centerMarkerCharOffsetX, - self.vectorEnd["graphicsitem"].y() - + self.vectorStart["centerCursorY"] - + self.centerMarkerCharOffsetY, - ) - - def updatePoint(self, point, posRBV, motID): - """Updates a point on the screen - - Updates the position of a point (e.g. self.vectorStart) drawn on the screen based on - which motor was moved (motID) using gonio position (posRBV) - """ - if point is None: - return point - centerMarkerOffsetX = point["centerCursorX"] - self.centerMarker.x() - centerMarkerOffsetY = point["centerCursorY"] - self.centerMarker.y() - startYY = point["coords"]["z"] - startYX = point["coords"]["y"] - startX = point["coords"]["x"] - - if motID == "omega": - newY = self.calculateNewYCoordPos(startYX, startYY) - point["graphicsitem"].setPos( - point["graphicsitem"].x(), newY - centerMarkerOffsetY - ) - if motID == "x": - delta = startX - posRBV - newX = float(self.screenXmicrons2pixels(delta)) - point["graphicsitem"].setPos( - newX - centerMarkerOffsetX, point["graphicsitem"].y() - ) - if motID == "y" or motID == "z": - newY = self.calculateNewYCoordPos(startYX, startYY) - point["graphicsitem"].setPos( - point["graphicsitem"].x(), newY - centerMarkerOffsetY - ) - return point + offset = (self.centerMarkerCharOffsetX, self.centerMarkerCharOffsetY) + self.vector_widget.update_vector(posRBV, motID, self.centerMarker.pos(), offset) def queueEnScanCB(self): self.protoComboBox.setCurrentIndex(self.protoComboBox.findText(str("eScan"))) @@ -2303,27 +2255,22 @@ def rasterStepChanged(self, text): self.beamHeight_ledit.setText(text) def updateVectorLengthAndSpeed(self): - x_vec_end = self.vectorEnd["coords"]["x"] - y_vec_end = self.vectorEnd["coords"]["y"] - z_vec_end = self.vectorEnd["coords"]["z"] - x_vec_start = self.vectorStart["coords"]["x"] - y_vec_start = self.vectorStart["coords"]["y"] - z_vec_start = self.vectorStart["coords"]["z"] - x_vec = x_vec_end - x_vec_start - y_vec = y_vec_end - y_vec_start - z_vec = z_vec_end - z_vec_start - trans_total = math.sqrt(x_vec**2 + y_vec**2 + z_vec**2) - if daq_utils.beamline == "nyx": - trans_total *= 1000 - self.vecLenLabelOutput.setText(str(int(trans_total))) - totalExpTime = ( - float(self.osc_end_ledit.text()) / float(self.osc_range_ledit.text()) - ) * float( - self.exp_time_ledit.text() - ) # (range/inc)*exptime - speed = trans_total / totalExpTime - self.vecSpeedLabelOutput.setText(str(int(speed))) - return x_vec, y_vec, z_vec, trans_total + osc_end = float(self.osc_end_ledit.text()) + osc_range = float(self.osc_range_ledit.text()) + exposure_time = float(self.exp_time_ledit.text()) + + ( + x_vec, + y_vec, + z_vec, + vector_length, + vector_speed, + ) = self.vector_widget.get_length_and_speed( + osc_end=osc_end, osc_range=osc_range, exposure_time=exposure_time + ) + self.vecLenLabelOutput.setText(str(int(vector_length))) + self.vecSpeedLabelOutput.setText(str(int(vector_speed))) + return x_vec, y_vec, z_vec, vector_length def totalExpChanged(self, text): if text == "oscEnd" and daq_utils.beamline == "fmx": @@ -4150,8 +4097,8 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): x_vec, y_vec, z_vec, trans_total = self.updateVectorLengthAndSpeed() framesPerPoint = int(self.vectorFPP_ledit.text()) vectorParams = { - "vecStart": self.vectorStart["coords"], - "vecEnd": self.vectorEnd["coords"], + "vecStart": self.vector_widget.vector_start["coords"], + "vecEnd": self.vector_widget.vector_end["coords"], "x_vec": x_vec, "y_vec": y_vec, "z_vec": z_vec, @@ -4160,10 +4107,10 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): } reqObj["vectorParams"] = vectorParams except Exception as e: - if self.vectorStart == None: + if self.vector_widget.vector_start == None: self.popupServerMessage("Vector start must be defined.") return - elif self.vectorEnd == None: + elif self.vector_widget.vector_end == None: self.popupServerMessage("Vector end must be defined.") return logger.error("Exception while getting vector parameters: %s" % e) @@ -4252,164 +4199,47 @@ def removePuckCB(self): dewarPos, ok = DewarDialog.getDewarPos(parent=self, action="remove") self.timerSample.start(SAMPLE_TIMER_DELAY) - def transform_vector_coords(self, prev_coords, current_raw_coords): - """Updates y and z co-ordinates of vector points when they are moved - - This function tweaks the y and z co-ordinates such that when a vector start or - end point is adjusted in the 2-D plane of the screen, it maintains the points' location - in the 3rd dimension perpendicular to the screen - - Args: - prev_coords: Dictionary with x,y and z co-ordinates of the previous location of the sample - current_raw_coords: Dictionary with x, y and z co-ordinates of the sample derived from the goniometer - PVs - omega: Omega of the Goniometer (usually RBV) - - Returns: - A dictionary mapping x, y and z to tweaked coordinates - """ - - # Transform z from prev point and y from current point to lab coordinate system - _, _, zLabPrev, _ = daq_utils.gonio2lab( - prev_coords["x"], - prev_coords["y"], - prev_coords["z"], - current_raw_coords["omega"], - ) - _, yLabCurrent, _, _ = daq_utils.gonio2lab( - current_raw_coords["x"], - current_raw_coords["y"], - current_raw_coords["z"], - current_raw_coords["omega"], - ) - - # Take y co-ordinate from current point and z-coordinate from prev point and transform back to gonio co-ordinates - _, yTweakedCurrent, zTweakedCurrent, _ = daq_utils.lab2gonio( - prev_coords["x"], yLabCurrent, zLabPrev, current_raw_coords["omega"] - ) - return { - "x": current_raw_coords["x"], - "y": yTweakedCurrent, - "z": zTweakedCurrent, - } - - def getVectorObject( - self, prevVectorPoint=None, gonioCoords=None, pen=None, brush=None, pointName=None - ): - """Creates and returns a vector start or end point - - Places a start or end vector marker wherever the crosshair is located in - the sample camera view and returns a dictionary of metadata related to that point - - Args: - prevVectorPoint: Dictionary of metadata related to a point being adjusted. For example, - a previously placed vectorStart point is moved, its old position is used to determine - its new co-ordinates in 3D space - gonioCoords: Dictionary of gonio coordinates. If not provided will retrieve current PV values - pen: QPen object that defines the color of the point's outline - brush: QBrush object that defines the color of the point's fill color - Returns: - A dict mapping the following keys - "coords": A dictionary of tweaked x, y and z positions of the Goniometer - "raw_coords": A dictionary of x, y, z co-ordinates obtained from the Goniometer PVs - "graphicsitem": Qt object referring to the marker on the sample camera - "centerCursorX" and "centerCursorY": Location of the center marker when this marker was placed - """ - if not pen: - pen = QtGui.QPen(QtCore.Qt.blue) - if not brush: - brush = QtGui.QBrush(QtCore.Qt.blue) - markWidth = 10 - # TODO: Place vecMarker in such a way that it matches any arbitrary gonioCoords given to this function - # currently vecMarker will be placed at the center of the sample cam - marker_x = self.centerMarker.x() - (markWidth / 2.0) - 1 + self.centerMarkerCharOffsetX - marker_y = self.centerMarker.y() - (markWidth / 2.0) - 1 + self.centerMarkerCharOffsetY - - vecMarker = VectorMarker(marker_x, marker_y, markWidth, markWidth, pen=pen, - brush=brush, parent=self, pointName=pointName) - self.scene.addItem(vecMarker) - if not gonioCoords: - gonioCoords = { - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omegaRBV_pv.get(), - } - if prevVectorPoint: - vectorCoords = self.transform_vector_coords( - prevVectorPoint["coords"], gonioCoords - ) - else: - vectorCoords = { - k: v for k, v in gonioCoords.items() if k in ["x", "y", "z"] - } - return { - "coords": vectorCoords, - "gonioCoords": gonioCoords, - "graphicsitem": vecMarker, - "centerCursorX": self.centerMarker.x(), - "centerCursorY": self.centerMarker.y(), - } - def setVectorPointCB(self, pointName): """Callback function to update a vector point Callback to remove or add the appropriate vector start or end point on the sample camera. - Calls getVectorObject to generate metadata related to this point. Draws a vector line if + Calls getObject to generate metadata related to this point. Draws a vector line if both start and end is defined Args: - point: Point to be placed (Either vectorStart or vectorEnd) + pointName: Name of the point to be placed (Either "vectorStart" or "vectorEnd") """ - point = getattr(self, pointName) - if point: - self.scene.removeItem(point["graphicsitem"]) - if self.vecLine: - self.scene.removeItem(self.vecLine) - if pointName == "vectorEnd": - brush = QtGui.QBrush(QtCore.Qt.red) - else: - brush = QtGui.QBrush(QtCore.Qt.blue) - point = self.getVectorObject(prevVectorPoint=point, brush=brush, pointName=pointName) - setattr(self, pointName, point) - if self.vectorStart and self.vectorEnd: - self.drawVector() + gonio_coords = { + "x": self.sampx_pv.get(), + "y": self.sampy_pv.get(), + "z": self.sampz_pv.get(), + "omega": self.omegaRBV_pv.get(), + } + center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX + center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY + self.vector_widget.set_vector_point( + point_name=pointName, + scene=self.scene, + gonio_coords=gonio_coords, + center=(center_x, center_y), + ) def drawVector(self): - pen = QtGui.QPen(QtCore.Qt.blue) try: self.updateVectorLengthAndSpeed() except: pass self.protoVectorRadio.setChecked(True) - self.vecLine = self.scene.addLine( - self.centerMarker.x() - + self.vectorStart["graphicsitem"].x() - + self.centerMarkerCharOffsetX, - self.centerMarker.y() - + self.vectorStart["graphicsitem"].y() - + self.centerMarkerCharOffsetY, - self.centerMarker.x() - + self.vectorEnd["graphicsitem"].x() - + self.centerMarkerCharOffsetX, - self.centerMarker.y() - + self.vectorEnd["graphicsitem"].y() - + self.centerMarkerCharOffsetY, - pen, - ) + center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX + center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY + center = (center_x, center_y) + + self.vector_widget.draw_vector(center, self.scene) def clearVectorCB(self): - if self.vectorStart: - self.scene.removeItem(self.vectorStart["graphicsitem"]) - self.vectorStart = None - if self.vectorEnd: - self.scene.removeItem(self.vectorEnd["graphicsitem"]) - self.vectorEnd = None - self.vecLenLabelOutput.setText("---") - self.vecSpeedLabelOutput.setText("---") - if self.vecLine: - self.scene.removeItem(self.vecLine) - self.vecLine = None + self.vector_widget.clear_vector(self.scene) + self.vecLenLabelOutput.setText("---") + self.vecSpeedLabelOutput.setText("---") def puckToDewarCB(self): while 1: diff --git a/gui/vector.py b/gui/vector.py index 2ac20486..d48976d5 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -1,44 +1,314 @@ +import typing from qtpy import QtWidgets, QtGui, QtCore +import numpy as np import daq_utils +if typing.TYPE_CHECKING: + from gui.control_main import ControlMain + + class VectorMarker(QtWidgets.QGraphicsEllipseItem): def __init__(self, *args, **kwargs): - brush = kwargs.pop('brush', QtGui.QBrush(QtCore.Qt.blue)) - pen = kwargs.pop('pen', QtGui.QPen(QtCore.Qt.blue)) - self.parent = kwargs.pop('parent', None) - self.pointName = kwargs.pop('pointName', None) + self.blue_color = QtCore.Qt.GlobalColor.blue + brush = kwargs.pop("brush", QtGui.QBrush()) + pen = kwargs.pop("pen", QtGui.QPen(self.blue_color)) + self.parent = kwargs.pop("parent", None) + self.point_name = kwargs.pop("point_name", None) + self.coords = kwargs.pop("coords") + self.gonio_coords = kwargs.pop("gonio_coords") + self.center_marker = kwargs.pop("center_marker") super().__init__(*args, **kwargs) self.setBrush(brush) self.setPen(pen) - self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable) - self.setFlag(QtWidgets.QGraphicsEllipseItem.ItemSendsGeometryChanges) - - + self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + self.setFlag( + QtWidgets.QGraphicsEllipseItem.GraphicsItemFlag.ItemSendsGeometryChanges + ) + def itemChange(self, change, value): - if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged: + if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: if self.parent and self.parent.vecLine: self.parent.scene.removeItem(self.parent.vecLine) self.parent.drawVector() return super().itemChange(change, value) - - + def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: print("New position:", self.pos()) if self.parent: micron_x = self.parent.screenXPixels2microns(self.pos().x()) micron_y = self.parent.screenYPixels2microns(self.pos().y()) - if self.pointName and getattr(self.parent, self.pointName): + if self.point_name and getattr(self.parent, self.point_name): omega = self.parent.omegaRBV_pv.get() - point = getattr(self.parent, self.pointName) - gonio_offset_x, gonio_offset_y, gonio_offset_z, omega = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) - gonioCoords = {"x": self.parent.sampx_pv.get() + gonio_offset_x, - "y": self.parent.sampy_pv.get() + gonio_offset_y, - "z": self.parent.sampz_pv.get() + gonio_offset_z, - "omega": omega} + point: VectorMarker = getattr(self.parent, self.point_name) + ( + gonio_offset_x, + gonio_offset_y, + gonio_offset_z, + omega, + ) = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) + gonio_coords = { + "x": self.parent.sampx_pv.get() + gonio_offset_x, + "y": self.parent.sampy_pv.get() + gonio_offset_y, + "z": self.parent.sampz_pv.get() + gonio_offset_z, + "omega": omega, + } vectorCoords = self.parent.transform_vector_coords( - point["coords"], gonioCoords - ) - point["coords"] = vectorCoords - point["gonioCoords"] = gonioCoords + point.coords, gonio_coords + ) + point.coords = vectorCoords + point.gonio_coords = gonio_coords + + return super().mouseReleaseEvent(event) + + +class VectorWidget(QtWidgets.QWidget): + def __init__( + self, + parent: QtWidgets.QWidget | None = None, + flags=..., + scene: QtWidgets.QGraphicsScene = ..., + main_window: ControlMain = ..., + ) -> None: + super().__init__(parent, flags) + self.main_window = main_window + self.vector_start: "None | VectorMarker" = None + self.vector_end: "None | VectorMarker" = None + self.vector_line: "None | QtWidgets.QGraphicsLineItem" = None + self.blue_color = QtCore.Qt.GlobalColor.blue + self.red_color = QtCore.Qt.GlobalColor.red + + def update_point( + self, + point: VectorMarker, + pos_rbv: int, + mot_id: str, + center_marker: QtCore.QPointF, + ): + """Updates a point on the screen + + Updates the position of a point (e.g. self.vector_start) drawn on the screen based on + which motor was moved (motID) using gonio position (posRBV) + """ + if point is None: + return point + centerMarkerOffsetX = point.center_marker.x() - center_marker.x() + centerMarkerOffsetY = point.center_marker.y() - center_marker.y() + startYY = point.coords["z"] + startYX = point.coords["y"] + startX = point.coords["x"] + + if mot_id == "omega": + newY = self.main_window.calculateNewYCoordPos(startYX, startYY) + point.setPos(point.x(), newY - centerMarkerOffsetY) + if mot_id == "x": + delta = startX - pos_rbv + newX = float(self.main_window.screenXmicrons2pixels(delta)) + point.setPos(newX - centerMarkerOffsetX, point.y()) + if mot_id == "y" or mot_id == "z": + newY = self.main_window.calculateNewYCoordPos(startYX, startYY) + point.setPos(point.x(), newY - centerMarkerOffsetY) + return point + + def update_vector( + self, + pos_rbv: int, + mot_id: str, + center_marker: QtCore.QPointF, + offset: tuple[int, int], + ): + if ( + self.vector_start is not None + and self.vector_end is not None + and self.vector_line is not None + ): + self.vector_start = self.update_point( + self.vector_start, pos_rbv, mot_id, center_marker + ) + self.vector_end = self.update_point( + self.vector_end, pos_rbv, mot_id, center_marker + ) + self.vector_line.setLine( + self.vector_start.x() + self.vector_start.center_marker.x() + offset[0], + self.vector_start.y() + self.vector_start.center_marker.y() + offset[1], + self.vector_end.x() + self.vector_start.center_marker.x() + offset[0], + self.vector_end.y() + self.vector_start.center_marker.y() + offset[1], + ) + + def get_length(self) -> tuple[int, int, int, np.floating[typing.Any]]: + trans_total = np.floating(0.0) + x_vec = y_vec = z_vec = 0 + + if self.vector_start and self.vector_end: + vec_end = np.array(list(map(self.vector_end.coords.get, ("x", "y", "z")))) + vec_start = np.array( + list(map(self.vector_start.coords.get, ("x", "y", "z"))) + ) + + vec_diff = vec_end - vec_start + trans_total = np.linalg.norm(vec_diff) + if daq_utils.beamline == "nyx": + trans_total *= 1000 + + return x_vec, y_vec, z_vec, trans_total + + def get_length_and_speed( + self, osc_end: float, osc_range: float, exposure_time: float + ) -> tuple[int, int, int, np.floating[typing.Any], np.floating[typing.Any]]: + total_exposure_time: float = (osc_end / osc_range) * exposure_time + x_vec, y_vec, z_vec, vector_length = self.get_length() + speed = vector_length / total_exposure_time + + return x_vec, y_vec, z_vec, vector_length, speed + + def set_vector_point( + self, + point_name: str, + scene: QtWidgets.QGraphicsScene, + gonio_coords: "dict[str, typing.Any]", + center: "tuple[float, float]", + ): + point = getattr(self, point_name) + if point: + scene.removeItem(point["graphicsitem"]) + if self.vector_line: + scene.removeItem(self.vector_line) + if point_name == "vector_end": + brush = QtGui.QBrush(self.red_color) + else: + brush = QtGui.QBrush(self.blue_color) + point = self.setup_vector_object( + gonio_coords, + center, + prev_vector_point=point, + brush=brush, + point_name=point_name, + ) + setattr(self, point_name, point) + scene.addItem(point) + if self.vector_start and self.vector_end: + self.draw_vector(center, scene) + + def draw_vector(self, center: tuple[float, float], scene: QtWidgets.QGraphicsScene): + pen = QtGui.QPen(self.blue_color) + + if self.vector_start is not None and self.vector_end is not None: + self.vecLine = scene.addLine( + center[0] + self.vector_start.x(), + center[1] + self.vector_start.y(), + center[0] + self.vector_end.x(), + center[1] + self.vector_end.y(), + pen, + ) + + def setup_vector_object( + self, + gonio_coords: "dict[str, typing.Any]", + center: "tuple[float, float]", + prev_vector_point: "None | VectorMarker" = None, + pen: "None | QtGui.QPen" = None, + brush: "None | QtGui.QBrush" = None, + point_name: "None | str" = None, + ) -> VectorMarker: + """Creates and returns a vector start or end point + + Places a start or end vector marker wherever the crosshair is located in + the sample camera view and returns a dictionary of metadata related to that point + + Args: + prev_vector_point: Dictionary of metadata related to a point being adjusted. For example, + a previously placed vector_start point is moved, its old position is used to determine + its new co-ordinates in 3D space + gonio_coords: Dictionary of gonio coordinates. If not provided will retrieve current PV values + pen: QPen object that defines the color of the point's outline + brush: QBrush object that defines the color of the point's fill color + Returns: + A VectorMarker along with the following metadata + "coords": A dictionary of tweaked x, y and z positions of the Goniometer + "gonio_coords": A dictionary of x, y, z co-ordinates obtained from the Goniometer PVs + "center_marker": Location of the center marker when this marker was placed + """ + if not pen: + pen = QtGui.QPen(self.blue_color) + if not brush: + brush = QtGui.QBrush(self.blue_color) + markWidth = 10 + marker_x = center[0] - (markWidth / 2.0) - 1 + marker_y = center[1] - (markWidth / 2.0) - 1 + + if prev_vector_point: + vectorCoords = self.transform_vector_coords( + prev_vector_point.coords, gonio_coords + ) + else: + vectorCoords = { + k: v for k, v in gonio_coords.items() if k in ["x", "y", "z"] + } + + vecMarker = VectorMarker( + marker_x, + marker_y, + markWidth, + markWidth, + pen=pen, + brush=brush, + parent=self, + point_name=point_name, + coords=vectorCoords, + gonio_coords=gonio_coords, + center_marker=self.main_window.centerMarker, + ) + return vecMarker + + def clear_vector(self, scene: QtWidgets.QGraphicsScene): + if self.vector_start: + scene.removeItem(self.vector_start) + self.vector_start = None + if self.vector_end: + scene.removeItem(self.vector_end) + self.vector_end = None + if self.vecLine: + scene.removeItem(self.vecLine) + self.vecLine = None + + def transform_vector_coords( + self, prev_coords: "dict[str, float]", current_raw_coords: "dict[str, float]" + ): + """Updates y and z co-ordinates of vector points when they are moved + + This function tweaks the y and z co-ordinates such that when a vector start or + end point is adjusted in the 2-D plane of the screen, it maintains the points' location + in the 3rd dimension perpendicular to the screen + + Args: + prev_coords: Dictionary with x,y and z co-ordinates of the previous location of the sample + current_raw_coords: Dictionary with x, y and z co-ordinates of the sample derived from the goniometer + PVs + omega: Omega of the Goniometer (usually RBV) + + Returns: + A dictionary mapping x, y and z to tweaked coordinates + """ + + # Transform z from prev point and y from current point to lab coordinate system + _, _, zLabPrev, _ = daq_utils.gonio2lab( + prev_coords["x"], + prev_coords["y"], + prev_coords["z"], + current_raw_coords["omega"], + ) + _, yLabCurrent, _, _ = daq_utils.gonio2lab( + current_raw_coords["x"], + current_raw_coords["y"], + current_raw_coords["z"], + current_raw_coords["omega"], + ) - return super().mouseReleaseEvent(event) \ No newline at end of file + # Take y co-ordinate from current point and z-coordinate from prev point and transform back to gonio co-ordinates + _, yTweakedCurrent, zTweakedCurrent, _ = daq_utils.lab2gonio( + prev_coords["x"], yLabCurrent, zLabPrev, current_raw_coords["omega"] + ) + return { + "x": current_raw_coords["x"], + "y": yTweakedCurrent, + "z": zTweakedCurrent, + } From d1ea3cff951c62247cf1117b0d54ff13f0ff40b4 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Sun, 23 Jul 2023 19:14:59 -0400 Subject: [PATCH 003/234] Fixes to make vector drag work --- gui/control_main.py | 2 +- gui/vector.py | 46 ++++++++++++++++++++++----------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index b71029ff..13916ddb 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -203,8 +203,8 @@ def __init__(self): self.raddoseTimer.setInterval(1000) self.raddoseTimer.timeout.connect(self.spawnRaddoseThread) - self.createSampleTab() self.vector_widget = VectorWidget(main_window=self) + self.createSampleTab() self.initCallbacks() if self.scannerType != "PI": diff --git a/gui/vector.py b/gui/vector.py index d48976d5..069f1aac 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -12,7 +12,7 @@ def __init__(self, *args, **kwargs): self.blue_color = QtCore.Qt.GlobalColor.blue brush = kwargs.pop("brush", QtGui.QBrush()) pen = kwargs.pop("pen", QtGui.QPen(self.blue_color)) - self.parent = kwargs.pop("parent", None) + self.parent: VectorWidget = kwargs.pop("parent", None) self.point_name = kwargs.pop("point_name", None) self.coords = kwargs.pop("coords") self.gonio_coords = kwargs.pop("gonio_coords") @@ -27,18 +27,18 @@ def __init__(self, *args, **kwargs): def itemChange(self, change, value): if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: - if self.parent and self.parent.vecLine: - self.parent.scene.removeItem(self.parent.vecLine) - self.parent.drawVector() + if self.parent and self.parent.vector_line: + self.parent.main_window.scene.removeItem(self.parent.vector_line) + self.parent.main_window.drawVector() return super().itemChange(change, value) def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: print("New position:", self.pos()) if self.parent: - micron_x = self.parent.screenXPixels2microns(self.pos().x()) - micron_y = self.parent.screenYPixels2microns(self.pos().y()) + micron_x = self.parent.main_window.screenXPixels2microns(self.pos().x()) + micron_y = self.parent.main_window.screenYPixels2microns(self.pos().y()) if self.point_name and getattr(self.parent, self.point_name): - omega = self.parent.omegaRBV_pv.get() + omega = self.parent.main_window.omegaRBV_pv.get() point: VectorMarker = getattr(self.parent, self.point_name) ( gonio_offset_x, @@ -47,9 +47,9 @@ def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: omega, ) = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) gonio_coords = { - "x": self.parent.sampx_pv.get() + gonio_offset_x, - "y": self.parent.sampy_pv.get() + gonio_offset_y, - "z": self.parent.sampz_pv.get() + gonio_offset_z, + "x": self.parent.main_window.sampx_pv.get() + gonio_offset_x, + "y": self.parent.main_window.sampy_pv.get() + gonio_offset_y, + "z": self.parent.main_window.sampz_pv.get() + gonio_offset_z, "omega": omega, } vectorCoords = self.parent.transform_vector_coords( @@ -64,12 +64,10 @@ def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: class VectorWidget(QtWidgets.QWidget): def __init__( self, - parent: QtWidgets.QWidget | None = None, - flags=..., - scene: QtWidgets.QGraphicsScene = ..., - main_window: ControlMain = ..., + main_window: "ControlMain", + parent: "QtWidgets.QWidget | None" = None, ) -> None: - super().__init__(parent, flags) + super().__init__(parent) self.main_window = main_window self.vector_start: "None | VectorMarker" = None self.vector_end: "None | VectorMarker" = None @@ -114,7 +112,7 @@ def update_vector( pos_rbv: int, mot_id: str, center_marker: QtCore.QPointF, - offset: tuple[int, int], + offset: "tuple[int, int]", ): if ( self.vector_start is not None @@ -134,8 +132,8 @@ def update_vector( self.vector_end.y() + self.vector_start.center_marker.y() + offset[1], ) - def get_length(self) -> tuple[int, int, int, np.floating[typing.Any]]: - trans_total = np.floating(0.0) + def get_length(self) -> "tuple[int, int, int, np.floating[typing.Any]]": + trans_total = 0.0 x_vec = y_vec = z_vec = 0 if self.vector_start and self.vector_end: @@ -153,7 +151,7 @@ def get_length(self) -> tuple[int, int, int, np.floating[typing.Any]]: def get_length_and_speed( self, osc_end: float, osc_range: float, exposure_time: float - ) -> tuple[int, int, int, np.floating[typing.Any], np.floating[typing.Any]]: + ) -> "tuple[int, int, int, np.floating[typing.Any], np.floating[typing.Any]]": total_exposure_time: float = (osc_end / osc_range) * exposure_time x_vec, y_vec, z_vec, vector_length = self.get_length() speed = vector_length / total_exposure_time @@ -188,11 +186,11 @@ def set_vector_point( if self.vector_start and self.vector_end: self.draw_vector(center, scene) - def draw_vector(self, center: tuple[float, float], scene: QtWidgets.QGraphicsScene): + def draw_vector(self, center: "tuple[float, float]", scene: QtWidgets.QGraphicsScene): pen = QtGui.QPen(self.blue_color) if self.vector_start is not None and self.vector_end is not None: - self.vecLine = scene.addLine( + self.vector_line = scene.addLine( center[0] + self.vector_start.x(), center[1] + self.vector_start.y(), center[0] + self.vector_end.x(), @@ -266,9 +264,9 @@ def clear_vector(self, scene: QtWidgets.QGraphicsScene): if self.vector_end: scene.removeItem(self.vector_end) self.vector_end = None - if self.vecLine: - scene.removeItem(self.vecLine) - self.vecLine = None + if self.vector_line: + scene.removeItem(self.vector_line) + self.vector_line = None def transform_vector_coords( self, prev_coords: "dict[str, float]", current_raw_coords: "dict[str, float]" From d3886ad8682e806e469ffac7de5b36d9b849c3b9 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Sun, 23 Jul 2023 20:16:04 -0400 Subject: [PATCH 004/234] Added signals to vector marker to handle changes --- gui/vector.py | 68 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index 069f1aac..d738c35c 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -8,6 +8,9 @@ class VectorMarker(QtWidgets.QGraphicsEllipseItem): + marker_pos_changed = QtCore.Signal(object) + marker_dropped = QtCore.Signal(object) + def __init__(self, *args, **kwargs): self.blue_color = QtCore.Qt.GlobalColor.blue brush = kwargs.pop("brush", QtGui.QBrush()) @@ -27,37 +30,12 @@ def __init__(self, *args, **kwargs): def itemChange(self, change, value): if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: - if self.parent and self.parent.vector_line: - self.parent.main_window.scene.removeItem(self.parent.vector_line) - self.parent.main_window.drawVector() + self.marker_pos_changed.emit(value) return super().itemChange(change, value) def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: print("New position:", self.pos()) - if self.parent: - micron_x = self.parent.main_window.screenXPixels2microns(self.pos().x()) - micron_y = self.parent.main_window.screenYPixels2microns(self.pos().y()) - if self.point_name and getattr(self.parent, self.point_name): - omega = self.parent.main_window.omegaRBV_pv.get() - point: VectorMarker = getattr(self.parent, self.point_name) - ( - gonio_offset_x, - gonio_offset_y, - gonio_offset_z, - omega, - ) = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) - gonio_coords = { - "x": self.parent.main_window.sampx_pv.get() + gonio_offset_x, - "y": self.parent.main_window.sampy_pv.get() + gonio_offset_y, - "z": self.parent.main_window.sampz_pv.get() + gonio_offset_z, - "omega": omega, - } - vectorCoords = self.parent.transform_vector_coords( - point.coords, gonio_coords - ) - point.coords = vectorCoords - point.gonio_coords = gonio_coords - + self.marker_dropped.emit(self) return super().mouseReleaseEvent(event) @@ -186,7 +164,9 @@ def set_vector_point( if self.vector_start and self.vector_end: self.draw_vector(center, scene) - def draw_vector(self, center: "tuple[float, float]", scene: QtWidgets.QGraphicsScene): + def draw_vector( + self, center: "tuple[float, float]", scene: QtWidgets.QGraphicsScene + ): pen = QtGui.QPen(self.blue_color) if self.vector_start is not None and self.vector_end is not None: @@ -255,8 +235,40 @@ def setup_vector_object( gonio_coords=gonio_coords, center_marker=self.main_window.centerMarker, ) + vecMarker.marker_dropped.connect(self.update_marker_position) + vecMarker.marker_pos_changed.connect(self.update_vector_position) return vecMarker + def update_vector_position(self, value): + if self.vector_line: + self.main_window.scene.removeItem(self.vector_line) + self.main_window.drawVector() + + def update_marker_position(self, point: VectorMarker): + # First convert the distance moved by the point from pixels to microns + micron_x = self.main_window.screenXPixels2microns(point.pos().x()) + micron_y = self.main_window.screenYPixels2microns(point.pos().y()) + omega = self.main_window.omegaRBV_pv.get() + + # Then translate the delta from microns in the lab co-ordinate system to gonio + ( + gonio_offset_x, + gonio_offset_y, + gonio_offset_z, + omega, + ) = daq_utils.lab2gonio(micron_x, -micron_y, 0, omega) + + # Then add the delta to the current gonio co-ordinates + gonio_coords = { + "x": self.main_window.sampx_pv.get() + gonio_offset_x, + "y": self.main_window.sampy_pv.get() + gonio_offset_y, + "z": self.main_window.sampz_pv.get() + gonio_offset_z, + "omega": omega, + } + vectorCoords = self.transform_vector_coords(point.coords, gonio_coords) + point.coords = vectorCoords + point.gonio_coords = gonio_coords + def clear_vector(self, scene: QtWidgets.QGraphicsScene): if self.vector_start: scene.removeItem(self.vector_start) From 9ae3a204612a4f742392426faa7712df9adfb948 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Sun, 23 Jul 2023 20:45:09 -0400 Subject: [PATCH 005/234] Added signal fixes and mouse hover cursor changes --- gui/vector.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index d738c35c..d6cc5ab3 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -6,11 +6,13 @@ if typing.TYPE_CHECKING: from gui.control_main import ControlMain - -class VectorMarker(QtWidgets.QGraphicsEllipseItem): +class VectorMarkerSignals(QtCore.QObject): marker_pos_changed = QtCore.Signal(object) marker_dropped = QtCore.Signal(object) + +class VectorMarker(QtWidgets.QGraphicsEllipseItem): + def __init__(self, *args, **kwargs): self.blue_color = QtCore.Qt.GlobalColor.blue brush = kwargs.pop("brush", QtGui.QBrush()) @@ -27,16 +29,31 @@ def __init__(self, *args, **kwargs): self.setFlag( QtWidgets.QGraphicsEllipseItem.GraphicsItemFlag.ItemSendsGeometryChanges ) + self.signals = VectorMarkerSignals() def itemChange(self, change, value): if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: - self.marker_pos_changed.emit(value) + self.signals.marker_pos_changed.emit(value) return super().itemChange(change, value) def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: print("New position:", self.pos()) - self.marker_dropped.emit(self) + self.signals.marker_dropped.emit(self) return super().mouseReleaseEvent(event) + + def hoverEnterEvent(self, event): + cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) + self.setCursor(cursor) + super().hoverEnterEvent(event) + + def hoverLeaveEvent(self, event): + self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + super().hoverLeaveEvent(event) + + def mousePressEvent(self, event: "QGraphicsSceneMouseEvent") -> None: + cursor = QtGui.QCursor(QtCore.Qt.ClosedHandCursor) + self.setCursor(cursor) + super().mousePressEvent(event) class VectorWidget(QtWidgets.QWidget): @@ -235,8 +252,8 @@ def setup_vector_object( gonio_coords=gonio_coords, center_marker=self.main_window.centerMarker, ) - vecMarker.marker_dropped.connect(self.update_marker_position) - vecMarker.marker_pos_changed.connect(self.update_vector_position) + vecMarker.signals.marker_dropped.connect(self.update_marker_position) + vecMarker.signals.marker_pos_changed.connect(self.update_vector_position) return vecMarker def update_vector_position(self, value): From 19892d46ebd1bc71df9d1663dcc4e1992f63486a Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 26 Jul 2023 13:23:30 -0400 Subject: [PATCH 006/234] Fixed issue where the point variable is accessed like a dict --- gui/vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/vector.py b/gui/vector.py index d6cc5ab3..2dd64b1d 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -162,7 +162,7 @@ def set_vector_point( ): point = getattr(self, point_name) if point: - scene.removeItem(point["graphicsitem"]) + scene.removeItem(point) if self.vector_line: scene.removeItem(self.vector_line) if point_name == "vector_end": From bc7a05de14678f0ca01978487a9c439b5a490315 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 26 Jul 2023 16:56:14 -0400 Subject: [PATCH 007/234] Added alternate cursor in case open hand doesnt exist --- gui/vector.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index 2dd64b1d..21f8d268 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -6,13 +6,13 @@ if typing.TYPE_CHECKING: from gui.control_main import ControlMain + class VectorMarkerSignals(QtCore.QObject): marker_pos_changed = QtCore.Signal(object) marker_dropped = QtCore.Signal(object) class VectorMarker(QtWidgets.QGraphicsEllipseItem): - def __init__(self, *args, **kwargs): self.blue_color = QtCore.Qt.GlobalColor.blue brush = kwargs.pop("brush", QtGui.QBrush()) @@ -40,10 +40,12 @@ def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: print("New position:", self.pos()) self.signals.marker_dropped.emit(self) return super().mouseReleaseEvent(event) - + def hoverEnterEvent(self, event): cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) self.setCursor(cursor) + if self.cursor.shape() != cursor: + self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor) super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): From 7c6bc4779244c83c5f31e9dbd10b1c1e4802d5c8 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 26 Jul 2023 17:12:32 -0400 Subject: [PATCH 008/234] Added set accept on hover --- gui/vector.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index 21f8d268..654fc268 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -30,6 +30,7 @@ def __init__(self, *args, **kwargs): QtWidgets.QGraphicsEllipseItem.GraphicsItemFlag.ItemSendsGeometryChanges ) self.signals = VectorMarkerSignals() + self.setAcceptHoverEvents(True) def itemChange(self, change, value): if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: @@ -37,15 +38,14 @@ def itemChange(self, change, value): return super().itemChange(change, value) def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: - print("New position:", self.pos()) + cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) + self.setCursor(cursor) self.signals.marker_dropped.emit(self) return super().mouseReleaseEvent(event) def hoverEnterEvent(self, event): cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) self.setCursor(cursor) - if self.cursor.shape() != cursor: - self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor) super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): @@ -180,6 +180,7 @@ def set_vector_point( ) setattr(self, point_name, point) scene.addItem(point) + point.setZValue(2.0) if self.vector_start and self.vector_end: self.draw_vector(center, scene) From 46cb653d6cab370c76d865ee7a18a7d75e39a035 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 1 Aug 2023 14:05:14 -0400 Subject: [PATCH 009/234] Cleaned up and modularized raster drawing --- gui/control_main.py | 237 ++++++++++++++++++-------------------------- 1 file changed, 99 insertions(+), 138 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 13916ddb..1da51bb2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3276,35 +3276,25 @@ def definePolyRaster( logger.error("bad value for beam width or beam height") self.popupServerMessage("bad value for beam width or beam height") return + rasterDef = { + "rasterType": "normal", + "beamWidth": beamWidth, + "beamHeight": beamHeight, + "status": RasterStatus.NEW.value, + "x": self.sampx_pv.get(), + "y": self.sampy_pv.get(), + "z": self.sampz_pv.get(), + "omega": self.omega_pv.get(), + "stepsize": stepsize, + "rowDefs": [], + } # just storing step as microns, not using here if self.scannerType == "PI": - rasterDef = { - "rasterType": "normal", - "beamWidth": beamWidth, - "beamHeight": beamHeight, - "status": RasterStatus.NEW.value, - "x": self.sampx_pv.get() + self.sampFineX_pv.get(), - "y": self.sampy_pv.get() + self.sampFineY_pv.get(), - "z": self.sampz_pv.get() + self.sampFineZ_pv.get(), - "omega": self.omega_pv.get(), - "stepsize": stepsize, - "rowDefs": [], - } # just storing step as microns, not using her - else: - rasterDef = { - "rasterType": "normal", - "beamWidth": beamWidth, - "beamHeight": beamHeight, - "status": RasterStatus.NEW.value, - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omega_pv.get(), - "stepsize": stepsize, - "rowDefs": [], - } # just storing step as microns, not using here - numsteps_h = int( - raster_w / stepsizeXPix - ) # raster_w = width,goes to numsteps horizonatl + rasterDef["x"] += self.sampFineX_pv.get() + rasterDef["y"] += self.sampFineY_pv.get() + rasterDef["z"] += self.sampFineZ_pv.get() + + # raster_w = width,goes to numsteps horizonatl + numsteps_h = int(raster_w / stepsizeXPix) numsteps_v = int(raster_h / stepsizeYPix) if numsteps_h == 2: numsteps_h = 1 # fix slop in user single line attempt @@ -3315,66 +3305,44 @@ def definePolyRaster( rasterDef["numCells"] = numsteps_h * numsteps_v point_offset_x = -(numsteps_h * stepsizeXPix) / 2 point_offset_y = -(numsteps_v * stepsizeYPix) / 2 + + vertical_raster = False + direction = numsteps_v if (numsteps_h == 1) or ( numsteps_v > numsteps_h and getBlConfig("vertRasterOn") - ): # vertical raster - for i in range(numsteps_h): - rowCellCount = 0 - for j in range(numsteps_v): - newCellX = point_x + (i * stepsizeXPix) + point_offset_x - newCellY = point_y + (j * stepsizeYPix) + point_offset_y - if rowCellCount == 0: # start of a new row - rowStartX = newCellX - rowStartY = newCellY - rowCellCount = rowCellCount + 1 - if ( - rowCellCount != 0 - ): # test for no points in this row of the bounding rect are in the poly? - vectorStartX = self.screenXPixels2microns( - rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX - ) - vectorEndX = vectorStartX - vectorStartY = self.screenYPixels2microns( - rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY - ) - vectorEndY = vectorStartY + self.screenYPixels2microns( - rowCellCount * stepsizeYPix - ) - newRowDef = { - "start": {"x": vectorStartX, "y": vectorStartY}, - "end": {"x": vectorEndX, "y": vectorEndY}, - "numsteps": rowCellCount, - } - rasterDef["rowDefs"].append(newRowDef) - else: # horizontal raster - for i in range(numsteps_v): - rowCellCount = 0 - for j in range(numsteps_h): - newCellX = point_x + (j * stepsizeXPix) + point_offset_x - newCellY = point_y + (i * stepsizeYPix) + point_offset_y - if rowCellCount == 0: # start of a new row - rowStartX = newCellX - rowStartY = newCellY - rowCellCount = rowCellCount + 1 - if ( - rowCellCount != 0 - ): # testing for no points in this row of the bounding rect are in the poly? - vectorStartX = self.screenXPixels2microns( - rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX - ) - vectorEndX = vectorStartX + self.screenXPixels2microns( - rowCellCount * stepsizeXPix - ) # this looks better - vectorStartY = self.screenYPixels2microns( - rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY - ) - vectorEndY = vectorStartY - newRowDef = { - "start": {"x": vectorStartX, "y": vectorStartY}, - "end": {"x": vectorEndX, "y": vectorEndY}, - "numsteps": rowCellCount, - } - rasterDef["rowDefs"].append(newRowDef) + ): + vertical_raster = True + direction = numsteps_h + + for i in range(direction): + rowCellCount = 0 + rowStartX = point_x + point_offset_x + rowStartY = point_y + point_offset_y + if vertical_raster: + rowStartX += i * stepsizeXPix + rowCellCount = numsteps_v + else: + rowStartY += i * stepsizeYPix + rowCellCount = numsteps_h + vectorStartX = self.screenXPixels2microns( + rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX + ) + vectorStartY = self.screenYPixels2microns( + rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY + ) + + vectorEndX = vectorStartX + vectorEndY = vectorStartY + if vertical_raster: + vectorEndY += self.screenYPixels2microns(rowCellCount * stepsizeYPix) + else: + vectorEndX += self.screenXPixels2microns(rowCellCount * stepsizeXPix) + newRowDef = { + "start": {"x": vectorStartX, "y": vectorStartY}, + "end": {"x": vectorEndX, "y": vectorEndY}, + "numsteps": rowCellCount, + } + rasterDef["rowDefs"].append(newRowDef) setBlConfig("rasterDefaultWidth", float(self.osc_range_ledit.text())) setBlConfig("rasterDefaultTime", float(self.exp_time_ledit.text())) setBlConfig("rasterDefaultTrans", float(self.transmission_ledit.text())) @@ -3389,6 +3357,49 @@ def rasterIsDrawn(self, rasterReq): return True return False + def draw_raster(self, raster_def: "dict[str, Any]", raster_dir: str) -> RasterGroup: + """ + This method is only concerned with creating the raster graphics + + Arguments: + raster_def : dict containing start and end points in microns for each row of the raster + + Returns: + raster_group : Raster graphics object of type RasterGroup + """ + stepsizeX = self.screenXmicrons2pixels(raster_def["stepsize"]) + stepsizeY = self.screenYmicrons2pixels(raster_def["stepsize"]) + pen = QtGui.QPen(QtCore.Qt.red) + newRasterCellList = [] + offset = { + "x": self.centerMarker.x() + self.centerMarkerCharOffsetX, + "y": self.centerMarker.y() + self.centerMarkerCharOffsetY, + } + for i in range(len(raster_def["rowDefs"])): + newCellX = ( + self.screenXmicrons2pixels(raster_def["rowDefs"][i]["start"]["x"]) + + offset["x"] + ) + newCellY = ( + self.screenYmicrons2pixels(raster_def["rowDefs"][i]["start"]["y"]) + + offset["y"] + ) + for j in range(raster_def["rowDefs"][i]["numsteps"]): + if raster_dir == "horizontal": + newCellX += j * stepsizeX + else: + newCellY += j * stepsizeY + + newCell = RasterCell( + int(newCellX), int(newCellY), stepsizeX, stepsizeY, self + ) + newRasterCellList.append(newCell) + newCell.setPen(pen) + newItemGroup = RasterGroup(self) + for i in range(len(newRasterCellList)): + newItemGroup.addToGroup(newRasterCellList[i]) + return newItemGroup + def drawPolyRaster( self, rasterReq, x=-1, y=-1, z=-1 ): # rasterDef in microns,offset from center, need to convert to pixels to draw, mainly this is for displaying autoRasters, but also called in zoom change @@ -3396,11 +3407,6 @@ def drawPolyRaster( rasterDef = rasterReq["request_obj"]["rasterDef"] except KeyError: return - beamSize = self.screenXmicrons2pixels(rasterDef["beamWidth"]) - stepsizeX = self.screenXmicrons2pixels(rasterDef["stepsize"]) - stepsizeY = self.screenYmicrons2pixels(rasterDef["stepsize"]) - pen = QtGui.QPen(QtCore.Qt.red) - newRasterCellList = [] try: if ( rasterDef["rowDefs"][0]["start"]["y"] @@ -3411,54 +3417,9 @@ def drawPolyRaster( rasterDir = "vertical" except IndexError: return - for i in range(len(rasterDef["rowDefs"])): - rowCellCount = 0 - for j in range(rasterDef["rowDefs"][i]["numsteps"]): - if rasterDir == "horizontal": - newCellX = ( - self.screenXmicrons2pixels( - rasterDef["rowDefs"][i]["start"]["x"] - ) - + (j * stepsizeX) - + self.centerMarker.x() - + self.centerMarkerCharOffsetX - ) - newCellY = ( - self.screenYmicrons2pixels( - rasterDef["rowDefs"][i]["start"]["y"] - ) - + self.centerMarker.y() - + self.centerMarkerCharOffsetY - ) - else: - newCellX = ( - self.screenXmicrons2pixels( - rasterDef["rowDefs"][i]["start"]["x"] - ) - + self.centerMarker.x() - + self.centerMarkerCharOffsetX - ) - newCellY = ( - self.screenYmicrons2pixels( - rasterDef["rowDefs"][i]["start"]["y"] - ) - + (j * stepsizeY) - + self.centerMarker.y() - + self.centerMarkerCharOffsetY - ) - if rowCellCount == 0: # start of a new row - rowStartX = newCellX - rowStartY = newCellY - newCellX = int(newCellX) - newCellY = int(newCellY) - newCell = RasterCell(newCellX, newCellY, stepsizeX, stepsizeY, self) - newRasterCellList.append(newCell) - newCell.setPen(pen) - rowCellCount = rowCellCount + 1 # really just for test of new row - newItemGroup = RasterGroup(self) + newItemGroup = self.draw_raster(rasterDef, rasterDir) self.scene.addItem(newItemGroup) - for i in range(len(newRasterCellList)): - newItemGroup.addToGroup(newRasterCellList[i]) + newRasterGraphicsDesc = { "uid": rasterReq["uid"], "coords": { From ca71e5b4dbdd5ff03e4560d72d0bddea1a39e788 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Tue, 1 Aug 2023 15:08:56 -0400 Subject: [PATCH 010/234] Fixed issue with adding vector data --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 1da51bb2..e9e54fc7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -4058,8 +4058,8 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): x_vec, y_vec, z_vec, trans_total = self.updateVectorLengthAndSpeed() framesPerPoint = int(self.vectorFPP_ledit.text()) vectorParams = { - "vecStart": self.vector_widget.vector_start["coords"], - "vecEnd": self.vector_widget.vector_end["coords"], + "vecStart": self.vector_widget.vector_start.coords, + "vecEnd": self.vector_widget.vector_end.coords, "x_vec": x_vec, "y_vec": y_vec, "z_vec": z_vec, From a572102fd396e56849179750396dd294de069525 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 5 Jun 2024 18:32:33 -0400 Subject: [PATCH 011/234] Fixed raster drawing --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index e7f393dc..3924f540 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3359,9 +3359,9 @@ def draw_raster(self, raster_def: "dict[str, Any]", raster_dir: str) -> RasterGr ) for j in range(raster_def["rowDefs"][i]["numsteps"]): if raster_dir == "horizontal": - newCellX += j * stepsizeX + newCellX += stepsizeX else: - newCellY += j * stepsizeY + newCellY += stepsizeY newCell = RasterCell( int(newCellX), int(newCellY), stepsizeX, stepsizeY, self From 070822fe1d6148deeb48bc753c7cee1614d14ef6 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 5 Jun 2024 20:33:08 -0400 Subject: [PATCH 012/234] Added full vector button --- gui/control_main.py | 23 +++++++++++++++++------ gui/vector.py | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 3924f540..9169476d 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -836,6 +836,9 @@ def createSampleTab(self): setVectorEndButton = QtWidgets.QPushButton("Vector\nEnd") setVectorEndButton.setStyleSheet("background-color: red") setVectorEndButton.clicked.connect(lambda: self.setVectorPointCB("vector_end")) + + setVectorButton = QtWidgets.QPushButton("Set\nVector") + setVectorButton.clicked.connect(lambda: self.setVectorPointCB("full_vector")) self.vecLine = None vectorFPPLabel = QtWidgets.QLabel("Number of Wedges") self.vectorFPP_ledit = QtWidgets.QLineEdit("1") @@ -846,6 +849,7 @@ def createSampleTab(self): self.vecSpeedLabelOutput = QtWidgets.QLabel("---") hBoxVectorLayout1.addWidget(setVectorStartButton) hBoxVectorLayout1.addWidget(setVectorEndButton) + hBoxVectorLayout1.addWidget(setVectorButton) hBoxVectorLayout1.addWidget(vectorFPPLabel) hBoxVectorLayout1.addWidget(self.vectorFPP_ledit) hBoxVectorLayout1.addWidget(vecLenLabel) @@ -4157,12 +4161,19 @@ def setVectorPointCB(self, pointName): } center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY - self.vector_widget.set_vector_point( - point_name=pointName, - scene=self.scene, - gonio_coords=gonio_coords, - center=(center_x, center_y), - ) + if pointName == "full_vector": + self.vector_widget.set_vector( + scene=self.scene, + gonio_coords=gonio_coords, + center=(center_x, center_y), + ) + else: + self.vector_widget.set_vector_point( + point_name=pointName, + scene=self.scene, + gonio_coords=gonio_coords, + center=(center_x, center_y), + ) def drawVector(self): try: diff --git a/gui/vector.py b/gui/vector.py index 654fc268..609d1744 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -154,6 +154,24 @@ def get_length_and_speed( speed = vector_length / total_exposure_time return x_vec, y_vec, z_vec, vector_length, speed + + def set_vector(self, scene: QtWidgets.QGraphicsScene, + gonio_coords: "dict[str, typing.Any]", + center: "tuple[float, float]"): + gonio_coords_start = { + "x": gonio_coords["x"] - 20, + "y": gonio_coords["y"], + "z": gonio_coords["z"], + "omega": gonio_coords["omega"], + } + gonio_coords_end = { + "x": gonio_coords["x"] + 20, + "y": gonio_coords["y"], + "z": gonio_coords["z"], + "omega": gonio_coords["omega"], + } + self.set_vector_point("vector_start", scene, gonio_coords_start, center) + self.set_vector_point("vector_end", scene, gonio_coords_end, center) def set_vector_point( self, From 4a7f6adba326be155de71314129b4b39c8382de9 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 5 Jun 2024 21:05:44 -0400 Subject: [PATCH 013/234] Added processSampMove to update vector node positions --- gui/control_main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index 9169476d..9712caa2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -4167,6 +4167,9 @@ def setVectorPointCB(self, pointName): gonio_coords=gonio_coords, center=(center_x, center_y), ) + self.processSampMove(self.sampx_pv.get(), "x") + self.processSampMove(self.sampy_pv.get(), "y") + self.processSampMove(self.sampz_pv.get(), "z") else: self.vector_widget.set_vector_point( point_name=pointName, From 8fd68a9ccc3b8e152acb4b03ce3d575f29e8f42e Mon Sep 17 00:00:00 2001 From: Shekar V Date: Thu, 6 Jun 2024 11:11:39 -0400 Subject: [PATCH 014/234] Added quick vector widgets and fixed position updates --- gui/control_main.py | 26 +++++++++++++++++++------- gui/vector.py | 25 ++++++++++++++++--------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 9712caa2..6d628ba2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -837,8 +837,6 @@ def createSampleTab(self): setVectorEndButton.setStyleSheet("background-color: red") setVectorEndButton.clicked.connect(lambda: self.setVectorPointCB("vector_end")) - setVectorButton = QtWidgets.QPushButton("Set\nVector") - setVectorButton.clicked.connect(lambda: self.setVectorPointCB("full_vector")) self.vecLine = None vectorFPPLabel = QtWidgets.QLabel("Number of Wedges") self.vectorFPP_ledit = QtWidgets.QLineEdit("1") @@ -849,14 +847,27 @@ def createSampleTab(self): self.vecSpeedLabelOutput = QtWidgets.QLabel("---") hBoxVectorLayout1.addWidget(setVectorStartButton) hBoxVectorLayout1.addWidget(setVectorEndButton) - hBoxVectorLayout1.addWidget(setVectorButton) hBoxVectorLayout1.addWidget(vectorFPPLabel) hBoxVectorLayout1.addWidget(self.vectorFPP_ledit) hBoxVectorLayout1.addWidget(vecLenLabel) hBoxVectorLayout1.addWidget(self.vecLenLabelOutput) hBoxVectorLayout1.addWidget(vecSpeedLabel) hBoxVectorLayout1.addWidget(self.vecSpeedLabelOutput) - self.vectorParamsFrame.setLayout(hBoxVectorLayout1) + vector_widgets_layout = QtWidgets.QVBoxLayout() + vector_widgets_layout.addLayout(hBoxVectorLayout1) + hBoxVectorLayout2 = QtWidgets.QHBoxLayout() + setVectorButton = QtWidgets.QPushButton("Set Quick\nVector") + setVectorButton.clicked.connect(lambda: self.setVectorPointCB("full_vector")) + vector_length_label = QtWidgets.QLabel("Quick vector length (microns)") + self.vector_length_ledit = QtWidgets.QLineEdit("40") + self.vector_length_ledit.setValidator(QIntValidator(self)) + + hBoxVectorLayout2.addWidget(setVectorButton) + hBoxVectorLayout2.addWidget(vector_length_label) + hBoxVectorLayout2.addWidget(self.vector_length_ledit) + + vector_widgets_layout.addLayout(hBoxVectorLayout2) + self.vectorParamsFrame.setLayout(vector_widgets_layout) vBoxColParams1.addLayout(hBoxColParams1) vBoxColParams1.addLayout(hBoxColParams2) vBoxColParams1.addLayout(hBoxColParams25) @@ -4166,10 +4177,8 @@ def setVectorPointCB(self, pointName): scene=self.scene, gonio_coords=gonio_coords, center=(center_x, center_y), + length=int(self.vector_length_ledit.text()) ) - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") else: self.vector_widget.set_vector_point( point_name=pointName, @@ -4177,6 +4186,9 @@ def setVectorPointCB(self, pointName): gonio_coords=gonio_coords, center=(center_x, center_y), ) + self.processSampMove(self.sampx_pv.get(), "x") + self.processSampMove(self.sampy_pv.get(), "y") + self.processSampMove(self.sampz_pv.get(), "z") def drawVector(self): try: diff --git a/gui/vector.py b/gui/vector.py index 609d1744..694e56ba 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -1,6 +1,8 @@ import typing -from qtpy import QtWidgets, QtGui, QtCore + import numpy as np +from qtpy import QtCore, QtGui, QtWidgets + import daq_utils if typing.TYPE_CHECKING: @@ -111,17 +113,19 @@ def update_vector( center_marker: QtCore.QPointF, offset: "tuple[int, int]", ): - if ( - self.vector_start is not None - and self.vector_end is not None - and self.vector_line is not None - ): + if self.vector_start is not None: self.vector_start = self.update_point( self.vector_start, pos_rbv, mot_id, center_marker ) + if self.vector_end is not None: self.vector_end = self.update_point( self.vector_end, pos_rbv, mot_id, center_marker ) + if ( + self.vector_start is not None + and self.vector_end is not None + and self.vector_line is not None + ): self.vector_line.setLine( self.vector_start.x() + self.vector_start.center_marker.x() + offset[0], self.vector_start.y() + self.vector_start.center_marker.y() + offset[1], @@ -157,15 +161,16 @@ def get_length_and_speed( def set_vector(self, scene: QtWidgets.QGraphicsScene, gonio_coords: "dict[str, typing.Any]", - center: "tuple[float, float]"): + center: "tuple[float, float]", length=40): + offset = int(length/2) gonio_coords_start = { - "x": gonio_coords["x"] - 20, + "x": gonio_coords["x"] - offset, "y": gonio_coords["y"], "z": gonio_coords["z"], "omega": gonio_coords["omega"], } gonio_coords_end = { - "x": gonio_coords["x"] + 20, + "x": gonio_coords["x"] + offset, "y": gonio_coords["y"], "z": gonio_coords["z"], "omega": gonio_coords["omega"], @@ -360,3 +365,5 @@ def transform_vector_coords( "y": yTweakedCurrent, "z": zTweakedCurrent, } + "z": zTweakedCurrent, + } From e6be1792813ffd67030e6fb97fb75e0cf13861cb Mon Sep 17 00:00:00 2001 From: Shekar V Date: Fri, 7 Jun 2024 02:20:04 -0400 Subject: [PATCH 015/234] Fixed syntax error --- gui/vector.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index 694e56ba..68a388db 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -365,5 +365,3 @@ def transform_vector_coords( "y": yTweakedCurrent, "z": zTweakedCurrent, } - "z": zTweakedCurrent, - } From 6429a68eea0b7a9edaf949a1d39ac63514c162f3 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Fri, 14 Jun 2024 09:36:57 -0400 Subject: [PATCH 016/234] Added typing info --- gui/vector.py | 52 +++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/gui/vector.py b/gui/vector.py index 68a388db..9f062345 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -15,7 +15,7 @@ class VectorMarkerSignals(QtCore.QObject): class VectorMarker(QtWidgets.QGraphicsEllipseItem): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.blue_color = QtCore.Qt.GlobalColor.blue brush = kwargs.pop("brush", QtGui.QBrush()) pen = kwargs.pop("pen", QtGui.QPen(self.blue_color)) @@ -34,28 +34,28 @@ def __init__(self, *args, **kwargs): self.signals = VectorMarkerSignals() self.setAcceptHoverEvents(True) - def itemChange(self, change, value): + def itemChange(self, change, value) -> typing.Any: if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: self.signals.marker_pos_changed.emit(value) return super().itemChange(change, value) def mouseReleaseEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: - cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) + cursor = QtGui.QCursor(QtCore.Qt.CursorShape.OpenHandCursor) self.setCursor(cursor) self.signals.marker_dropped.emit(self) return super().mouseReleaseEvent(event) - def hoverEnterEvent(self, event): - cursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor) + def hoverEnterEvent(self, event) -> None: + cursor = QtGui.QCursor(QtCore.Qt.CursorShape.OpenHandCursor) self.setCursor(cursor) super().hoverEnterEvent(event) - def hoverLeaveEvent(self, event): - self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + def hoverLeaveEvent(self, event) -> None: + self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)) super().hoverLeaveEvent(event) - def mousePressEvent(self, event: "QGraphicsSceneMouseEvent") -> None: - cursor = QtGui.QCursor(QtCore.Qt.ClosedHandCursor) + def mousePressEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: + cursor = QtGui.QCursor(QtCore.Qt.CursorShape.ClosedHandCursor) self.setCursor(cursor) super().mousePressEvent(event) @@ -80,7 +80,7 @@ def update_point( pos_rbv: int, mot_id: str, center_marker: QtCore.QPointF, - ): + ) -> VectorMarker: """Updates a point on the screen Updates the position of a point (e.g. self.vector_start) drawn on the screen based on @@ -112,7 +112,7 @@ def update_vector( mot_id: str, center_marker: QtCore.QPointF, offset: "tuple[int, int]", - ): + ) -> None: if self.vector_start is not None: self.vector_start = self.update_point( self.vector_start, pos_rbv, mot_id, center_marker @@ -133,7 +133,7 @@ def update_vector( self.vector_end.y() + self.vector_start.center_marker.y() + offset[1], ) - def get_length(self) -> "tuple[int, int, int, np.floating[typing.Any]]": + def get_length(self) -> "tuple[int, int, int, np.floating[typing.Any] | float]": trans_total = 0.0 x_vec = y_vec = z_vec = 0 @@ -152,17 +152,21 @@ def get_length(self) -> "tuple[int, int, int, np.floating[typing.Any]]": def get_length_and_speed( self, osc_end: float, osc_range: float, exposure_time: float - ) -> "tuple[int, int, int, np.floating[typing.Any], np.floating[typing.Any]]": + ) -> "tuple[int, int, int, np.floating[typing.Any] | float, np.floating[typing.Any] | float]": total_exposure_time: float = (osc_end / osc_range) * exposure_time x_vec, y_vec, z_vec, vector_length = self.get_length() speed = vector_length / total_exposure_time return x_vec, y_vec, z_vec, vector_length, speed - - def set_vector(self, scene: QtWidgets.QGraphicsScene, - gonio_coords: "dict[str, typing.Any]", - center: "tuple[float, float]", length=40): - offset = int(length/2) + + def set_vector( + self, + scene: QtWidgets.QGraphicsScene, + gonio_coords: "dict[str, typing.Any]", + center: "tuple[float, float]", + length=40, + ) -> None: + offset = int(length / 2) gonio_coords_start = { "x": gonio_coords["x"] - offset, "y": gonio_coords["y"], @@ -184,7 +188,7 @@ def set_vector_point( scene: QtWidgets.QGraphicsScene, gonio_coords: "dict[str, typing.Any]", center: "tuple[float, float]", - ): + ) -> None: point = getattr(self, point_name) if point: scene.removeItem(point) @@ -209,7 +213,7 @@ def set_vector_point( def draw_vector( self, center: "tuple[float, float]", scene: QtWidgets.QGraphicsScene - ): + ) -> None: pen = QtGui.QPen(self.blue_color) if self.vector_start is not None and self.vector_end is not None: @@ -282,12 +286,12 @@ def setup_vector_object( vecMarker.signals.marker_pos_changed.connect(self.update_vector_position) return vecMarker - def update_vector_position(self, value): + def update_vector_position(self, value) -> None: if self.vector_line: self.main_window.scene.removeItem(self.vector_line) self.main_window.drawVector() - def update_marker_position(self, point: VectorMarker): + def update_marker_position(self, point: VectorMarker) -> None: # First convert the distance moved by the point from pixels to microns micron_x = self.main_window.screenXPixels2microns(point.pos().x()) micron_y = self.main_window.screenYPixels2microns(point.pos().y()) @@ -312,7 +316,7 @@ def update_marker_position(self, point: VectorMarker): point.coords = vectorCoords point.gonio_coords = gonio_coords - def clear_vector(self, scene: QtWidgets.QGraphicsScene): + def clear_vector(self, scene: QtWidgets.QGraphicsScene) -> None: if self.vector_start: scene.removeItem(self.vector_start) self.vector_start = None @@ -325,7 +329,7 @@ def clear_vector(self, scene: QtWidgets.QGraphicsScene): def transform_vector_coords( self, prev_coords: "dict[str, float]", current_raw_coords: "dict[str, float]" - ): + ) -> dict[str, float]: """Updates y and z co-ordinates of vector points when they are moved This function tweaks the y and z co-ordinates such that when a vector start or From 5383c17388bcfe538373f25e7a6d6b587b10f0b1 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Fri, 14 Jun 2024 09:37:21 -0400 Subject: [PATCH 017/234] Added letters to start and end nodes --- gui/vector.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gui/vector.py b/gui/vector.py index 9f062345..e1d63e22 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -59,6 +59,16 @@ def mousePressEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent) -> None: self.setCursor(cursor) super().mousePressEvent(event) + def paint(self, painter, option, widget): + super().paint(painter, option, widget) + rect = self.rect() + painter.setPen(QtGui.QPen(QtCore.Qt.GlobalColor.black)) + if self.point_name == "vector_start": + text = "S" + else: + text = "E" + painter.drawText(rect, QtCore.Qt.AlignmentFlag.AlignCenter, text) + class VectorWidget(QtWidgets.QWidget): def __init__( From 45c823d8416ea1cc4cfc308ec8127f2974a07da9 Mon Sep 17 00:00:00 2001 From: Jun Aishima Date: Mon, 17 Jun 2024 16:06:13 -0400 Subject: [PATCH 018/234] enable use of alternative ISPyB config files * use ISPYB_DB environment variable to enable use of a different ISPyB config file, such as for use of the ispyb-dev database * this will allow, for example, to test LSDC code that populates ISPyB by simply changing the ISPYB_DB env var --- ispybLib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ispybLib.py b/ispybLib.py index 456f9906..de1eaf1e 100644 --- a/ispybLib.py +++ b/ispybLib.py @@ -14,7 +14,8 @@ #12/19 - I'm leaving all commented lines alone on this. Karl Levik, DLS, is an immense help with this. -conf_file = "/etc/ispyb/ispybConfig.cfg" +ispyb_db_name = os.environ("ISPYB_DB", "ispyb") +conf_file = f"/etc/ispyb/{ispyb_db_name}Config.cfg" visit = 'mx99999-1' # Get a list of request dicts #request_dicts = lsdb2.getColRequestsByTimeInterval('2018-02-14T00:00:00','2018-02-15T00:00:00') From ddef424102d2add9ccd42e994bd5be73d61efda1 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 24 Jun 2024 10:52:55 -0400 Subject: [PATCH 019/234] Hide vector nodes when shift is held - Raster explore dialog is not focused when displayed - Update vector length and speed when a node is moved --- gui/control_main.py | 14 ++++++++++---- gui/dialog/raster_explore.py | 1 + gui/vector.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 6d628ba2..bbad77d5 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -247,6 +247,16 @@ def __init__(self): self.XRFInfoDict = self.parseXRFTable() # I don't like this # self.dewarTree.refreshTreeDewarView() + def eventFilter(self, obj, event): + # Event filter to hide vector nodes when shift is held. This is to allow the user to see + # the raster hidden by large vector nodes + if event.type() == QtCore.QEvent.KeyPress and event.key() == QtCore.Qt.Key.Key_Shift: + self.vector_widget.hide_nodes() + elif event.type() == QtCore.QEvent.KeyRelease and event.key() == QtCore.Qt.Key.Key_Shift: + self.vector_widget.show_nodes() + return QtWidgets.QWidget.eventFilter(self, obj, event) + + def setGuiValues(self, values): for item, value in values.items(): logger.info("resetting %s to %s" % (item, value)) @@ -4191,10 +4201,6 @@ def setVectorPointCB(self, pointName): self.processSampMove(self.sampz_pv.get(), "z") def drawVector(self): - try: - self.updateVectorLengthAndSpeed() - except: - pass self.protoVectorRadio.setChecked(True) center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY diff --git a/gui/dialog/raster_explore.py b/gui/dialog/raster_explore.py index e86f79e0..f0f221c8 100644 --- a/gui/dialog/raster_explore.py +++ b/gui/dialog/raster_explore.py @@ -39,6 +39,7 @@ def __init__(self): vBoxParams1.addLayout(hBoxParams3) vBoxParams1.addWidget(self.buttons) self.setLayout(vBoxParams1) + self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowDoesNotAcceptFocus) def setSpotCount(self, val): self.spotCount_ledit.setText(str(val)) diff --git a/gui/vector.py b/gui/vector.py index e1d63e22..391ddfc8 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -326,6 +326,9 @@ def update_marker_position(self, point: VectorMarker) -> None: point.coords = vectorCoords point.gonio_coords = gonio_coords + # Update vector length and speed in the GUI after any node moves + self.main_window.updateVectorLengthAndSpeed() + def clear_vector(self, scene: QtWidgets.QGraphicsScene) -> None: if self.vector_start: scene.removeItem(self.vector_start) @@ -379,3 +382,15 @@ def transform_vector_coords( "y": yTweakedCurrent, "z": zTweakedCurrent, } + + def hide_nodes(self): + if self.vector_start: + self.vector_start.setVisible(False) + if self.vector_end: + self.vector_end.setVisible(False) + + def show_nodes(self): + if self.vector_start: + self.vector_start.setVisible(True) + if self.vector_end: + self.vector_end.setVisible(True) From 14d5e19c52efff4fd4111e834a77994eab86d027 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 24 Jun 2024 11:18:52 -0400 Subject: [PATCH 020/234] Reverting raster changes --- gui/control_main.py | 237 ++++++++++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 99 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 96b0d5a8..2a0dca44 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3309,25 +3309,35 @@ def definePolyRaster( logger.error("bad value for beam width or beam height") self.popupServerMessage("bad value for beam width or beam height") return - rasterDef = { - "rasterType": "normal", - "beamWidth": beamWidth, - "beamHeight": beamHeight, - "status": RasterStatus.NEW.value, - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omega_pv.get(), - "stepsize": stepsize, - "rowDefs": [], - } # just storing step as microns, not using here if self.scannerType == "PI": - rasterDef["x"] += self.sampFineX_pv.get() - rasterDef["y"] += self.sampFineY_pv.get() - rasterDef["z"] += self.sampFineZ_pv.get() - - # raster_w = width,goes to numsteps horizonatl - numsteps_h = int(raster_w / stepsizeXPix) + rasterDef = { + "rasterType": "normal", + "beamWidth": beamWidth, + "beamHeight": beamHeight, + "status": RasterStatus.NEW.value, + "x": self.sampx_pv.get() + self.sampFineX_pv.get(), + "y": self.sampy_pv.get() + self.sampFineY_pv.get(), + "z": self.sampz_pv.get() + self.sampFineZ_pv.get(), + "omega": self.omega_pv.get(), + "stepsize": stepsize, + "rowDefs": [], + } # just storing step as microns, not using her + else: + rasterDef = { + "rasterType": "normal", + "beamWidth": beamWidth, + "beamHeight": beamHeight, + "status": RasterStatus.NEW.value, + "x": self.sampx_pv.get(), + "y": self.sampy_pv.get(), + "z": self.sampz_pv.get(), + "omega": self.omega_pv.get(), + "stepsize": stepsize, + "rowDefs": [], + } # just storing step as microns, not using here + numsteps_h = int( + raster_w / stepsizeXPix + ) # raster_w = width,goes to numsteps horizonatl numsteps_v = int(raster_h / stepsizeYPix) if numsteps_h == 2: numsteps_h = 1 # fix slop in user single line attempt @@ -3338,44 +3348,66 @@ def definePolyRaster( rasterDef["numCells"] = numsteps_h * numsteps_v point_offset_x = -(numsteps_h * stepsizeXPix) / 2 point_offset_y = -(numsteps_v * stepsizeYPix) / 2 - - vertical_raster = False - direction = numsteps_v if (numsteps_h == 1) or ( numsteps_v > numsteps_h and getBlConfig("vertRasterOn") - ): - vertical_raster = True - direction = numsteps_h - - for i in range(direction): - rowCellCount = 0 - rowStartX = point_x + point_offset_x - rowStartY = point_y + point_offset_y - if vertical_raster: - rowStartX += i * stepsizeXPix - rowCellCount = numsteps_v - else: - rowStartY += i * stepsizeYPix - rowCellCount = numsteps_h - vectorStartX = self.screenXPixels2microns( - rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX - ) - vectorStartY = self.screenYPixels2microns( - rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY - ) - - vectorEndX = vectorStartX - vectorEndY = vectorStartY - if vertical_raster: - vectorEndY += self.screenYPixels2microns(rowCellCount * stepsizeYPix) - else: - vectorEndX += self.screenXPixels2microns(rowCellCount * stepsizeXPix) - newRowDef = { - "start": {"x": vectorStartX, "y": vectorStartY}, - "end": {"x": vectorEndX, "y": vectorEndY}, - "numsteps": rowCellCount, - } - rasterDef["rowDefs"].append(newRowDef) + ): # vertical raster + for i in range(numsteps_h): + rowCellCount = 0 + for j in range(numsteps_v): + newCellX = point_x + (i * stepsizeXPix) + point_offset_x + newCellY = point_y + (j * stepsizeYPix) + point_offset_y + if rowCellCount == 0: # start of a new row + rowStartX = newCellX + rowStartY = newCellY + rowCellCount = rowCellCount + 1 + if ( + rowCellCount != 0 + ): # test for no points in this row of the bounding rect are in the poly? + vectorStartX = self.screenXPixels2microns( + rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX + ) + vectorEndX = vectorStartX + vectorStartY = self.screenYPixels2microns( + rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY + ) + vectorEndY = vectorStartY + self.screenYPixels2microns( + rowCellCount * stepsizeYPix + ) + newRowDef = { + "start": {"x": vectorStartX, "y": vectorStartY}, + "end": {"x": vectorEndX, "y": vectorEndY}, + "numsteps": rowCellCount, + } + rasterDef["rowDefs"].append(newRowDef) + else: # horizontal raster + for i in range(numsteps_v): + rowCellCount = 0 + for j in range(numsteps_h): + newCellX = point_x + (j * stepsizeXPix) + point_offset_x + newCellY = point_y + (i * stepsizeYPix) + point_offset_y + if rowCellCount == 0: # start of a new row + rowStartX = newCellX + rowStartY = newCellY + rowCellCount = rowCellCount + 1 + if ( + rowCellCount != 0 + ): # testing for no points in this row of the bounding rect are in the poly? + vectorStartX = self.screenXPixels2microns( + rowStartX - self.centerMarker.x() - self.centerMarkerCharOffsetX + ) + vectorEndX = vectorStartX + self.screenXPixels2microns( + rowCellCount * stepsizeXPix + ) # this looks better + vectorStartY = self.screenYPixels2microns( + rowStartY - self.centerMarker.y() - self.centerMarkerCharOffsetY + ) + vectorEndY = vectorStartY + newRowDef = { + "start": {"x": vectorStartX, "y": vectorStartY}, + "end": {"x": vectorEndX, "y": vectorEndY}, + "numsteps": rowCellCount, + } + rasterDef["rowDefs"].append(newRowDef) setBlConfig("rasterDefaultWidth", float(self.osc_range_ledit.text())) setBlConfig("rasterDefaultTime", float(self.exp_time_ledit.text())) setBlConfig("rasterDefaultTrans", float(self.transmission_ledit.text())) @@ -3390,49 +3422,6 @@ def rasterIsDrawn(self, rasterReq): return True return False - def draw_raster(self, raster_def: "dict[str, Any]", raster_dir: str) -> RasterGroup: - """ - This method is only concerned with creating the raster graphics - - Arguments: - raster_def : dict containing start and end points in microns for each row of the raster - - Returns: - raster_group : Raster graphics object of type RasterGroup - """ - stepsizeX = self.screenXmicrons2pixels(raster_def["stepsize"]) - stepsizeY = self.screenYmicrons2pixels(raster_def["stepsize"]) - pen = QtGui.QPen(QtCore.Qt.red) - newRasterCellList = [] - offset = { - "x": self.centerMarker.x() + self.centerMarkerCharOffsetX, - "y": self.centerMarker.y() + self.centerMarkerCharOffsetY, - } - for i in range(len(raster_def["rowDefs"])): - newCellX = ( - self.screenXmicrons2pixels(raster_def["rowDefs"][i]["start"]["x"]) - + offset["x"] - ) - newCellY = ( - self.screenYmicrons2pixels(raster_def["rowDefs"][i]["start"]["y"]) - + offset["y"] - ) - for j in range(raster_def["rowDefs"][i]["numsteps"]): - if raster_dir == "horizontal": - newCellX += stepsizeX - else: - newCellY += stepsizeY - - newCell = RasterCell( - int(newCellX), int(newCellY), stepsizeX, stepsizeY, self - ) - newRasterCellList.append(newCell) - newCell.setPen(pen) - newItemGroup = RasterGroup(self) - for i in range(len(newRasterCellList)): - newItemGroup.addToGroup(newRasterCellList[i]) - return newItemGroup - def drawPolyRaster( self, rasterReq, x=-1, y=-1, z=-1 ): # rasterDef in microns,offset from center, need to convert to pixels to draw, mainly this is for displaying autoRasters, but also called in zoom change @@ -3440,6 +3429,11 @@ def drawPolyRaster( rasterDef = rasterReq["request_obj"]["rasterDef"] except KeyError: return + beamSize = self.screenXmicrons2pixels(rasterDef["beamWidth"]) + stepsizeX = self.screenXmicrons2pixels(rasterDef["stepsize"]) + stepsizeY = self.screenYmicrons2pixels(rasterDef["stepsize"]) + pen = QtGui.QPen(QtCore.Qt.red) + newRasterCellList = [] try: if ( rasterDef["rowDefs"][0]["start"]["y"] @@ -3450,9 +3444,54 @@ def drawPolyRaster( rasterDir = "vertical" except IndexError: return - newItemGroup = self.draw_raster(rasterDef, rasterDir) + for i in range(len(rasterDef["rowDefs"])): + rowCellCount = 0 + for j in range(rasterDef["rowDefs"][i]["numsteps"]): + if rasterDir == "horizontal": + newCellX = ( + self.screenXmicrons2pixels( + rasterDef["rowDefs"][i]["start"]["x"] + ) + + (j * stepsizeX) + + self.centerMarker.x() + + self.centerMarkerCharOffsetX + ) + newCellY = ( + self.screenYmicrons2pixels( + rasterDef["rowDefs"][i]["start"]["y"] + ) + + self.centerMarker.y() + + self.centerMarkerCharOffsetY + ) + else: + newCellX = ( + self.screenXmicrons2pixels( + rasterDef["rowDefs"][i]["start"]["x"] + ) + + self.centerMarker.x() + + self.centerMarkerCharOffsetX + ) + newCellY = ( + self.screenYmicrons2pixels( + rasterDef["rowDefs"][i]["start"]["y"] + ) + + (j * stepsizeY) + + self.centerMarker.y() + + self.centerMarkerCharOffsetY + ) + if rowCellCount == 0: # start of a new row + rowStartX = newCellX + rowStartY = newCellY + newCellX = int(newCellX) + newCellY = int(newCellY) + newCell = RasterCell(newCellX, newCellY, stepsizeX, stepsizeY, self) + newRasterCellList.append(newCell) + newCell.setPen(pen) + rowCellCount = rowCellCount + 1 # really just for test of new row + newItemGroup = RasterGroup(self) self.scene.addItem(newItemGroup) - + for i in range(len(newRasterCellList)): + newItemGroup.addToGroup(newRasterCellList[i]) newRasterGraphicsDesc = { "uid": rasterReq["uid"], "coords": { From 6a3680fb95b0dd6966840eb601177e93288049fd Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 24 Jun 2024 11:23:45 -0400 Subject: [PATCH 021/234] Hide restart server button --- gui/control_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/control_main.py b/gui/control_main.py index 56f6a4f9..f296faf3 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -361,6 +361,7 @@ def createSampleTab(self): endVisitButton.clicked.connect(self.endVisitCB) restartServerButton = QtWidgets.QPushButton("Restart Server") restartServerButton.clicked.connect(self.restartServerCB) + restartServerButton.hide() self.openShutterButton = QtWidgets.QPushButton("Open Photon Shutter") self.openShutterButton.clicked.connect(self.openPhotonShutterCB) self.popUserScreen = QtWidgets.QPushButton("User Screen...") From b8babf3d89950ca9d56bec9fab349978c93ee4ad Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 1 Jul 2024 12:22:41 -0400 Subject: [PATCH 022/234] Added tested code for robot recovery procedure --- daq_macros.py | 10 ++++++++++ embl_robot.py | 23 +++++++++++++++++++---- robot_lib.py | 3 +++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index a83c7429..4e6da73a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -19,6 +19,7 @@ import parseSheet import attenCalc import raddoseLib +import robot_lib from raddoseLib import * import logging logger = logging.getLogger(__name__) @@ -248,6 +249,15 @@ def changeImageCenterHighMag(x,y,czoom): "face_on_omega" : 0 } +def run_robot_recovery_procedure(): + """ Generic recovery procedure to be used during automated + collection""" + # Recover robot + robot_lib.recoverRobot() + # Dry Gripper + robot_lib.dryGripper() + # Park Gripper and cool gripper + robot_lib.cooldownGripper() def run_top_view_optimized(): RE(topview_optimized()) diff --git a/embl_robot.py b/embl_robot.py index 9d540a15..fadd7beb 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -442,6 +442,7 @@ def unmount(self, gov_robot, puckPos, pinPos, sampID): absPos = (PINS_PER_PUCK*(puckPos%3))+pinPos+1 if getBlConfig('robot_online'): try: + logger.info("Unmounting sample") RobotControlLib.unmount2(absPos) except Exception as e: e_s = str(e) @@ -450,10 +451,24 @@ def unmount(self, gov_robot, puckPos, pinPos, sampID): daq_macros.disableMount() daq_lib.gui_message(e_s + ". FATAL ROBOT ERROR - CALL STAFF! robotOff() executed.") return UNMOUNT_FAILURE - message = "ROBOT unmount2 ERROR: " + e_s - daq_lib.gui_message(message) - logger.error(message) - return UNMOUNT_FAILURE + elif (e_s.find("SE") != -1): + # In case there is an SE Timeout error, run the recovery procedure + daq_macros.run_robot_recovery_procedure() + try: + # Try to unmount again + RobotControlLib.unmount2(absPos) + except Exception as e: + # If there is an exception again, return UNMOUNT_FAILURE + e_s = str(e) + message = "ROBOT unmount2 ERROR: " + e_s + daq_lib.gui_message(message) + logger.error(message) + return UNMOUNT_FAILURE + else: + message = "ROBOT unmount2 ERROR: " + e_s + daq_lib.gui_message(message) + logger.error(message) + return UNMOUNT_FAILURE gov_status = gov_lib.setGovRobot(gov_robot, 'SE') if not gov_status.success: daq_lib.clearMountedSample() diff --git a/robot_lib.py b/robot_lib.py index d9c4bff6..e553f9c2 100644 --- a/robot_lib.py +++ b/robot_lib.py @@ -133,3 +133,6 @@ def openGripper(): def closeGripper(): robot.closeGripper() + +def cooldownGripper(): + robot.cooldownGripper() \ No newline at end of file From 6ece1031913b2eeb0cbdd44b1f7b970e1b605560 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 1 Jul 2024 12:33:39 -0400 Subject: [PATCH 023/234] Added recovery if unmount attempt 2 is unsuccessful --- embl_robot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/embl_robot.py b/embl_robot.py index fadd7beb..e411a5bd 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -459,6 +459,7 @@ def unmount(self, gov_robot, puckPos, pinPos, sampID): RobotControlLib.unmount2(absPos) except Exception as e: # If there is an exception again, return UNMOUNT_FAILURE + daq_macros.run_robot_recovery_procedure() e_s = str(e) message = "ROBOT unmount2 ERROR: " + e_s daq_lib.gui_message(message) From 63fb3396746887789bb694e8fd2eaedf98b50fd7 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 2 Jul 2024 14:55:43 -0400 Subject: [PATCH 024/234] Using DETECTOR_SAFE_DISTANCE instead ot ROBOT_MIN_DISTANCE --- config_params.py | 3 --- daq_lib.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/config_params.py b/config_params.py index 8b2a9181..55310241 100644 --- a/config_params.py +++ b/config_params.py @@ -67,9 +67,6 @@ class OnMountAvailOptions(Enum): SAMPLE_TIMER_DELAY = 0 SERVER_CHECK_DELAY = 2000 -ROBOT_MIN_DISTANCE = 200.0 -ROBOT_DISTANCE_TOLERANCE = 0.050 - FAST_DP_MIN_NODES = 4 SPOT_MIN_NODES = 8 MOUNT_SUCCESSFUL = 1 diff --git a/daq_lib.py b/daq_lib.py index eb95b72b..e8ec44f2 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -378,7 +378,7 @@ def runDCQueue(): #maybe don't run rasters from here??? logger.info("processing request " + str(time.time())) reqObj = currentRequest["request_obj"] gov_lib.set_detz_in(gov_robot, reqObj["detDist"]) - if (reqObj["detDist"] >= ROBOT_MIN_DISTANCE and getBlConfig("HePath") == 0): + if (reqObj["detDist"] >= DETECTOR_SAFE_DISTANCE[daq_utils.beamline] and getBlConfig("HePath") == 0): gov_lib.set_detz_out(gov_robot, reqObj["detDist"]) sampleID = currentRequest["sample"] mountedSampleDict = db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample') From 7e132decaf84fbfb25e3e157e4df805f0112d1bb Mon Sep 17 00:00:00 2001 From: Shekar V Date: Fri, 5 Jul 2024 10:39:22 -0400 Subject: [PATCH 025/234] Apply suggestions from code review Co-authored-by: Jun Aishima --- embl_robot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/embl_robot.py b/embl_robot.py index e411a5bd..6b9b516f 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -451,7 +451,7 @@ def unmount(self, gov_robot, puckPos, pinPos, sampID): daq_macros.disableMount() daq_lib.gui_message(e_s + ". FATAL ROBOT ERROR - CALL STAFF! robotOff() executed.") return UNMOUNT_FAILURE - elif (e_s.find("SE") != -1): + elif "SE" in e_s: # In case there is an SE Timeout error, run the recovery procedure daq_macros.run_robot_recovery_procedure() try: @@ -460,8 +460,7 @@ def unmount(self, gov_robot, puckPos, pinPos, sampID): except Exception as e: # If there is an exception again, return UNMOUNT_FAILURE daq_macros.run_robot_recovery_procedure() - e_s = str(e) - message = "ROBOT unmount2 ERROR: " + e_s + message = f"ROBOT unmount2 ERROR: {e}" daq_lib.gui_message(message) logger.error(message) return UNMOUNT_FAILURE From 405fe21200ec5b36957b023a74f499b8f9568edf Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 16 Jul 2024 18:54:45 -0400 Subject: [PATCH 026/234] Retry and recovery code for loop detection --- mxbluesky/plans/loop_detection.py | 33 +++++++++++++++++++++++------ mxbluesky/plans/top_view.py | 35 ++++++++++++++++++++++--------- start_bs.py | 2 ++ 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/mxbluesky/plans/loop_detection.py b/mxbluesky/plans/loop_detection.py index 279cc66f..8a706381 100644 --- a/mxbluesky/plans/loop_detection.py +++ b/mxbluesky/plans/loop_detection.py @@ -6,8 +6,9 @@ import bluesky.plans as bp from bluesky.utils import FailedStatus from bluesky.preprocessors import finalize_decorator +from ophyd.utils.errors import WaitTimeoutError import daq_utils -from start_bs import db, two_click_low, loop_detector, gonio +from start_bs import db, two_click_low, loop_detector, gonio, low_mag_cam_reset_signal from mxbluesky.plans.utils import mvr_with_retry, mv_with_retry logger = getLogger() @@ -18,16 +19,35 @@ def cleanup_two_click_low(): yield from bps.abs_set(two_click_low.jpeg.file_write_mode, 2, wait=True) yield from bps.abs_set(two_click_low.jpeg.file_template, "%s%s_%d.jpg", wait=True) +def reset_low_mag_cam(): + yield from bps.abs_set(two_click_low.cam.acquire, 0, wait=True) + yield from bps.sleep(3) + yield from bps.abs_set(low_mag_cam_reset_signal, 1, wait=True) + yield from bps.sleep(5) + yield from bps.abs_set(two_click_low.cam.acquire, 1, wait=True) + +def trigger_two_click(): + tries = 0 + max_tries = 3 + + while tries < max_tries: + try: + yield from bp.count([two_click_low], 1) + break # If trigger succeeds, exit the loop + except (FailedStatus, WaitTimeoutError) as e: + tries += 1 + logger.exception(f"Exception while triggering two click, retry #{tries} : {e}") + if tries == 3: + logger.exception("Resetting low mag camera") + yield from reset_low_mag_cam() + yield from bp.count([two_click_low], 1) + @finalize_decorator(cleanup_two_click_low) def detect_loop(sample_detection: "Dict[str, float|int]"): # face on attempt, most features, should work yield from bps.abs_set(two_click_low.cam_mode, "two_click", wait=True) logger.info("Starting loop centering") - #two_click_low.cam_mode.set("two_click") - try: - yield from bp.count([two_click_low], 1) - except FailedStatus: - yield from bp.count([two_click_low], 1) + yield from trigger_two_click() loop_detector.filename.set(two_click_low.jpeg.full_file_name.get()) @@ -69,6 +89,7 @@ def detect_loop(sample_detection: "Dict[str, float|int]"): # orthogonal face, use loop model only if predicted width matches face on # otherwise, threshold yield from bps.mv(gonio.o, sample_detection["face_on_omega"]+90) + yield from trigger_two_click() try: yield from bp.count([two_click_low], 1) diff --git a/mxbluesky/plans/top_view.py b/mxbluesky/plans/top_view.py index 31a17c36..1f44be88 100644 --- a/mxbluesky/plans/top_view.py +++ b/mxbluesky/plans/top_view.py @@ -24,8 +24,14 @@ logger = getLogger() def cleanup_topcam(): - yield from bps.abs_set(top_aligner_slow.topcam.cam.acquire, 1, wait=True) - yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True) + try: + yield from bps.abs_set(top_aligner_slow.topcam.cam.acquire, 1, wait=True, timeout=4) + yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True, timeout=4) + except WaitTimeoutError as error: + logger.exception(f"Exception in cleanup_topcam, trying again: {error}") + yield from bps.abs_set(top_aligner_slow.topcam.cam.acquire, 1, wait=True, timeout=4) + yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True, timeout=4) + def inner_pseudo_fly_scan(*args, **kwargs): scan_uid = yield from bp.count(*args, **kwargs) @@ -74,6 +80,12 @@ def inner_pseudo_fly_scan(*args, **kwargs): return delta_y, delta_z, omega_min +def setup_transition_signals(target_state, zebra_dir, cam_mode): + yield from bps.abs_set(top_aligner_fast.target_gov_state, target_state, wait=True) + yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, zebra_dir, wait=True, timeout=4) + yield from bps.abs_set(top_aligner_fast.topcam.cam_mode, cam_mode, wait=True, timeout=4) + yield from bps.sleep(0.1) + @finalize_decorator(cleanup_topcam) def topview_optimized(): @@ -94,10 +106,12 @@ def topview_optimized(): logger.info("Updated TA work pos, starting transition to TA") # SE -> TA - yield from bps.abs_set(top_aligner_fast.target_gov_state, "TA", wait=True) - yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True) - yield from bps.abs_set(top_aligner_fast.topcam.cam_mode, CamMode.COARSE_ALIGN.value) - yield from bps.sleep(0.1) + try: + yield from setup_transition_signals("TA", 0, CamMode.COARSE_ALIGN.value) + except WaitTimeoutError as error: + logger.exception(f"Exception while setting SE to TA signals, trying again: {error}") + yield from setup_transition_signals("TA", 0, CamMode.COARSE_ALIGN.value) + logger.info("Starting 1st inner fly scan") try: @@ -124,10 +138,11 @@ def topview_optimized(): yield from set_SA_work_pos(delta_y, delta_z) logger.info("Starting transition to SA") # TA -> SA - yield from bps.abs_set(top_aligner_fast.target_gov_state, "SA", wait=True) - yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 1, wait=True) - yield from bps.abs_set(top_aligner_fast.topcam.cam_mode, CamMode.FINE_FACE.value) - yield from bps.sleep(0.1) + try: + yield from setup_transition_signals("SA", 1, CamMode.FINE_FACE.value) + except WaitTimeoutError as error: + logger.exception(f"Exception while setting TA to SA signals, trying again: {error}") + yield from setup_transition_signals("SA", 1, CamMode.FINE_FACE.value) logger.info("Starting 2nd inner fly scan") try: delta_y, delta_z, omega_min = yield from inner_pseudo_fly_scan( diff --git a/start_bs.py b/start_bs.py index ac163893..3d5f7499 100755 --- a/start_bs.py +++ b/start_bs.py @@ -138,6 +138,7 @@ class SampleXYZ(Device): work_pos = WorkPositions("XF:17IDB-ES:AMX", name="work_pos") mount_pos = MountPositions("XF:17IDB-ES:AMX", name="mount_pos") two_click_low = TwoClickLowMag("XF:17IDB-ES:AMX{Cam:6}", name="two_click_low") + low_mag_cam_reset_signal = EpicsSignal('XF:17IDB-CT:AMX{IOC:CAM06}:SysReset') gonio = GoniometerStack("XF:17IDB-ES:AMX{Gon:1", name="gonio") loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) @@ -170,6 +171,7 @@ class SampleXYZ(Device): work_pos = WorkPositions("XF:17IDC-ES:FMX", name="work_pos") mount_pos = MountPositions("XF:17IDC-ES:FMX", name="mount_pos") two_click_low = TwoClickLowMag("XF:17IDC-ES:FMX{Cam:7}", name="two_click_low") + low_mag_cam_reset_signal = EpicsSignal('XF:17IDC-CT:FMX{IOC:CAM07}:SysReset') gonio = GoniometerStack("XF:17IDC-ES:FMX{Gon:1", name="gonio") loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) From f48c981964e955a82bcfc4c55391be01fbd4fc44 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 04:47:51 -0700 Subject: [PATCH 027/234] gui split, fixes required for raster --- gui/dialog/staff_screen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index b0f673ab..8460ff3b 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -233,7 +233,10 @@ def show(self): def getSpotNodeList(self): nodeList = [] for i in range(0, self.spotNodeCount): - nodeList.append(int(getBlConfig("spotNode" + str(i + 1)).split("cpu")[1])) + if daq_utils.beamline == 'nyx': + nodeList.append(int(getBlConfig("spotNode"+str(i+1)).split('epu')[1])) + else: + nodeList.append(int(getBlConfig("spotNode"+str(i+1)).split('cpu')[1])) return nodeList def getFastDPNodeList(self): From fe6b16d723370d04d10a82719df0beff3c65c934 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Fri, 3 Feb 2023 16:34:57 -0500 Subject: [PATCH 028/234] Fixes epic address list error --- bin/lsdcServer_nyx.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lsdcServer_nyx.cmd b/bin/lsdcServer_nyx.cmd index b06b291b..47fe7b11 100755 --- a/bin/lsdcServer_nyx.cmd +++ b/bin/lsdcServer_nyx.cmd @@ -2,7 +2,7 @@ export PROJDIR=/nsls2/software/mx/daq/ export CONFIGDIR=${PROJDIR}bnlpx_config/ export LSDCHOME=${PROJDIR}lsdc_nyx -export EPICS_CA_AUTO_ADDR_LIST=NO +#export EPICS_CA_AUTO_ADDR_LIST=NO export PYTHONPATH=".:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" export PATH=/usr/local/bin:/usr/bin:/bin:${PROJDIR}/software/bin:/opt/ccp4/bin source ${CONFIGDIR}daq_env_nyx.txt From 06c690d872748fbe07ce476387ad4511e39967be Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 8 Feb 2023 14:50:03 -0500 Subject: [PATCH 029/234] Fixes an error in the multiSampleGripper definition for denso_robot --- denso_robot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/denso_robot.py b/denso_robot.py index 06693225..50baeef5 100644 --- a/denso_robot.py +++ b/denso_robot.py @@ -83,7 +83,7 @@ def unmount(self, gov_robot, puck_pos: int, pin_pos: int, samp_id: str): def finish(self): ... - def multiSampleGripper(): + def multiSampleGripper(self): return True def check_sample_mounted(self, mount, puck_pos, pin_pos): # is the correct sample present/absent as expected during a mount/unmount? From a00f36c6708ef94d354cbe394f2665873b272eb2 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 8 Feb 2023 16:36:33 -0500 Subject: [PATCH 030/234] Fixes double-gripper logic, breaks mounting of the same sample onto itself --- daq_lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_lib.py b/daq_lib.py index e8ec44f2..6221b8a0 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -257,8 +257,8 @@ def mountSample(sampID): setPvDesc("robotZWorkPos",getPvDesc("robotZMountPos")) setPvDesc("robotOmegaWorkPos",90.0) logger.info("done setting work pos") - if (currentMountedSampleID != ""): #then unmount what's there - if (sampID!=currentMountedSampleID and not robot_lib.multiSampleGripper()): + if (currentMountedSampleID != "" and not robot_lib.multiSampleGripper()): #then unmount what's there + if (sampID!=currentMountedSampleID): puckPos = mountedSampleDict["puckPos"] pinPos = mountedSampleDict["pinPos"] if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID): From 5f07c4cfbcfe1d034ba3f1cd81f3197a7e59c20c Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 05:01:57 -0700 Subject: [PATCH 031/234] adds backend for parkRobot --- denso_robot.py | 6 ++++++ robot_lib.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/denso_robot.py b/denso_robot.py index 50baeef5..1ea4c7bf 100644 --- a/denso_robot.py +++ b/denso_robot.py @@ -13,6 +13,12 @@ class OphydRobot: def __init__(self, robot): self.robot = robot + def parkRobot(self): + try: + self.robot.parkRobot() + except Exception as e: + logger.error(f'Failed to park robot: {e}') + def warmupGripper(self): try: logger.info('drying gripper') diff --git a/robot_lib.py b/robot_lib.py index e553f9c2..e92478c4 100644 --- a/robot_lib.py +++ b/robot_lib.py @@ -122,6 +122,9 @@ def closePorts(): def rebootEMBL(): robot.rebootEMBL() +def parkRobot(): + robot.parkRobot() + def parkGripper(): robot.parkGripper() From c7e2b3d6d27bb507f1eef96025e710925d84dc6d Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 8 Mar 2023 15:06:03 -0500 Subject: [PATCH 032/234] replaces old raster flyer with NYX raster flyer --- start_bs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start_bs.py b/start_bs.py index ac163893..609fada1 100755 --- a/start_bs.py +++ b/start_bs.py @@ -189,8 +189,8 @@ class SampleXYZ(Device): detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) from nyxtools.flyer_eiger2 import NYXEiger2Flyer flyer = NYXEiger2Flyer(vector, zebra, detector) - from mxtools.raster_flyer import MXRasterFlyer - raster_flyer = MXRasterFlyer(vector, zebra, detector) + from nyxtools.nyx_raster_flyer import NYXRasterFlyer + raster_flyer = NYXRasterFlyer(vector, zebra, detector) from nyxtools.isara_robot import IsaraRobotDevice from denso_robot import OphydRobot From 81d1dea647f1cb5455ad921c59d10719b70c7e19 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 4 May 2023 10:37:26 -0400 Subject: [PATCH 033/234] Added configuration for skipping the epics detector init --- config_params.py | 3 ++- daq_main_common.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config_params.py b/config_params.py index 55310241..ce691135 100644 --- a/config_params.py +++ b/config_params.py @@ -81,6 +81,7 @@ class OnMountAvailOptions(Enum): PINS_PER_PUCK = 16 DETECTOR_OBJECT_TYPE_LSDC = "lsdc" # using det_lib +DETECTOR_OBJECT_TYPE_NO_INIT = "no init" # skip epics detector init DETECTOR_OBJECT_TYPE_OPHYD = "ophyd" # instantiated in start_bs, using Bluesky scans DETECTOR_OBJECT_TYPE = "detectorObjectType" @@ -132,4 +133,4 @@ class OnMountAvailOptions(Enum): BEAMSIZE_OPTIONS = { "S": ["V0", "H0"], "L": ["V1", "H1"] -} \ No newline at end of file +} diff --git a/daq_main_common.py b/daq_main_common.py index d303e26d..d959bcad 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -112,8 +112,8 @@ def pybass_init(): daq_utils.init_environment() daq_lib.init_var_channels() - #if getBlConfig(config_params.DETECTOR_OBJECT_TYPE) == config_params.DETECTOR_OBJECT_TYPE_LSDC: - det_lib.init_detector() + if getBlConfig(config_params.DETECTOR_OBJECT_TYPE) != config_params.DETECTOR_OBJECT_TYPE_NO_INIT: + det_lib.init_detector() daq_lib.message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "message_string") daq_lib.gui_popup_message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "gui_popup_message_string") beamline_lib.read_db() From c6da0a9280b023da667b3045a87e1188824aed8c Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 05:33:03 -0700 Subject: [PATCH 034/234] daq_macros changes for code compliance merging --- daq_macros.py | 89 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 4e6da73a..637d3f57 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -32,13 +32,17 @@ from kafka_producer import send_kafka_message import gov_lib +import urllib.request +import io from scans import (zebra_daq_prep, setup_zebra_vector_scan, setup_zebra_vector_scan_for_raster, setup_vector_program) import bluesky.plan_stubs as bps import bluesky.plans as bp -from bluesky.preprocessors import finalize_wrapper, finalize_decorator +from bluesky.preprocessors import finalize_wrapper +from bluesky.log import config_bluesky_logging +config_bluesky_logging(level='INFO') from fmx_annealer import govStatusGet, govStateSet, fmxAnnealer, amxAnnealer # for using annealer specific to FMX and AMX from config_params import ON_MOUNT_OPTION, OnMountAvailOptions, BEAMSIZE_OPTIONS from mxbluesky.plans import detect_loop, topview_optimized @@ -397,7 +401,7 @@ def autoRasterLoop(currentRequest): def autoRasterLoopOld(currentRequest): global autoRasterFlag - + logger.info('entering autoRasterLoop') gov_status = gov_lib.setGovRobot(gov_robot, 'SA') if not gov_status.success: return 0 @@ -996,7 +1000,7 @@ def runDozorThread(directory, """ global rasterRowResultsList,processedRasterRowCount - time.sleep(0.5) #allow for file writing + time.sleep(1.0) #allow for file writing node = getNodeName("spot", rowIndex, 8) @@ -2047,17 +2051,23 @@ def snakeRasterBluesky(rasterReqID, grain=""): if govStatus.exception(): logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}") return - raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) - raster_flyer.detector.stage() + #raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) + #raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] + yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row - xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(row, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, row_index) - vector = {'x': (xMotAbsoluteMove, xEnd), 'y': (yMotAbsoluteMove, yEnd), 'z': (zMotAbsoluteMove, zEnd)} + logger.info(f'starting new row: {row_index}') + zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) + vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} + yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000) + yield from bps.mv(samplexyz.y, yMotAbsoluteMove/1000) + yield from bps.mv(samplexyz.z, zMotAbsoluteMove/1000) + yield from bps.mv(samplexyz.omega, omega-0.05) yield from zebraDaqRasterBluesky(raster_flyer, omega, numsteps, img_width_per_cell * numsteps, img_width_per_cell, exptimePerCell, rasterFilePrefix, data_directory_name, file_number_start, row_index, vector) - raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row - time.sleep(0.2) # necessary for reliable row processing - see comment in commit 6793f4 + #raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row + time.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4 # processing if (procFlag): if (daq_utils.detector_id == "EIGER-16"): @@ -2081,6 +2091,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): initiate transitions here allows for GUI sample/heat map image to update after moving to known position""" logger.debug(f'lastOnSample(): {lastOnSample()} autoRasterFlag: {autoRasterFlag}') + time.sleep(3) #waiting for detector to not lose row if (lastOnSample() and not autoRasterFlag): govStatus = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) if govStatus.exception(): @@ -2113,6 +2124,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): else: # yes, do row processing if daq_lib.abort_flag != 1: + print("processing rows") [thread.join(timeout=120) for thread in spotFindThreadList] else: logger.info("raster aborted, do not wait for spotfind threads") @@ -2125,7 +2137,12 @@ def snakeRasterBluesky(rasterReqID, grain=""): multiColThreshold = parentReqObj["diffCutoff"] else: multiColThreshold = reqObj["diffCutoff"] - gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) + #gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) + logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}") + yield from bps.mv(samplexyz.x, rasterStartX) + yield from bps.mv(samplexyz.y, rasterStartY) + yield from bps.mv(samplexyz.z, rasterStartZ) + yield from bps.mv(samplexyz.omega, omega) else: try: # go to start omega for faster heat map display @@ -2475,11 +2492,11 @@ def gotoMaxRaster(rasterResult,multiColThreshold=None,**kwargs): if max_index: x, y, z = run_auto_raster(max_index, score_vals, scoreOption, cellResults, rasterMap, **kwargs) if 'omega' in kwargs: - beamline_lib.mvaDescriptor("sampleX",x, - "sampleY",y, - "sampleZ",z, + beamline_lib.mvaDescriptor("sampleX",x/1000, + "sampleY",y/1000, + "sampleZ",z/1000, "omega",kwargs['omega']) - else: beamline_lib.mvaDescriptor("sampleX",x,"sampleY",y,"sampleZ",z) + else: beamline_lib.mvaDescriptor("sampleX",x/1000,"sampleY",y/1000,"sampleZ",z/1000) if (autoVectorFlag): #if we found a hotspot, then look again at cellResults for coarse vector start and end run_auto_vector(score_val, cellResults, scoreOption, rasterMap) @@ -3471,12 +3488,20 @@ def clean_up_files(pic_prefix, output_file): def loop_center_xrec(): global face_on + print('entering daq_macros.loop_center_xrec') daq_lib.abort_flag = 0 pic_prefix = "findloop" output_file = 'xrec_result.txt' + print('clean up files') clean_up_files(pic_prefix, output_file) - zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) + #TODO: if daq_utils.beamline=='nyx': + print('post clean') + xrec_no_zebra(0) + print('post no zebra') + #else: + # zebraCamDaq(0,360,40,.4,pic_prefix,os.getcwd(),0) + #zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) comm_s = f'xrec {os.environ["CONFIGDIR"]}/xrec_360_40Fast.txt {output_file}' logger.info(comm_s) try: @@ -3529,7 +3554,37 @@ def loop_center_xrec(): #now try to get the loopshape starting from here return 1 - +def xrec_no_zebra(angle_start): + print(f'xrec_no_zebra{angle_start}') + beamline_lib.mvaDescriptor("omega", angle_start) + #yield from bps.mv(samplexyz.omega, angle_start) + for omega_target in range (angle_start, angle_start+360, 40): + #yield from bps.mv(samplexyz.omega, omega_target) + beamline_lib.mvaDescriptor("omega", omega_target) + logger.info(f'taking image at {omega_target}') + timeout = 5 + # change image mode to single(0) + beamline_support.setPvValFromDescriptor("lowMagImMode",0) + # start camera + beamline_support.setPvValFromDescriptor("lowMagAcquire",1) + try: + with urllib.request.urlopen(daq_utils.lowMagCamURL, timeout=timeout) as response: + logger.info("xnz: read") + image_data = response.read() + logger.info("xnz: read") + with open("findloop_"+str(omega_target/40)+".jpg", "wb") as filename: + logger.info("xnz: write file") + filename.write(image_data) + logger.info("xnz: write file") + except urllib.error.URLError as e: + print("Error:", e) + logger.info("xnz: sleep") + time.sleep(1) + # change image mode to continuous(2) + beamline_support.setPvValFromDescriptor("lowMagImMode",2) + # start camera + beamline_support.setPvValFromDescriptor("lowMagAcquire",1) + def zebraCamDaq(angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega #careful - there's total exposure time, exposure period, exposure time @@ -3702,7 +3757,7 @@ def zebraDaqRasterBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, e row_index=row_index, transmission=1, protocol="raster") yield from bp.fly([raster_flyer]) - logger.info("vector Done") + logger.info(f"vector Done, zebra arm status: {raster_flyer.zebra.pc.arm.output}") logger.info("zebraDaqRasterBluesky Done") def zebraDaq(vector_program,angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3,changeState=True): #scan encoder 0=x, 1=y,2=z,3=omega From 57f0947e7918e16561a8becd6107239bfe31c17a Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 05:37:00 -0700 Subject: [PATCH 035/234] start_bs compliance merge changes --- start_bs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/start_bs.py b/start_bs.py index 609fada1..17901213 100755 --- a/start_bs.py +++ b/start_bs.py @@ -107,9 +107,9 @@ def filter_camera_data(camera): camera.stats5.read_attrs = ['total', 'centroid'] class SampleXYZ(Device): - x = Cpt(EpicsMotor, ':GX}Mtr') - y = Cpt(EpicsMotor, ':PY}Mtr') - z = Cpt(EpicsMotor, ':PZ}Mtr') + x = Cpt(EpicsMotor, ':X}Mtr') + y = Cpt(EpicsMotor, ':Y}Mtr') + z = Cpt(EpicsMotor, ':Z}Mtr') omega = Cpt(EpicsMotor, ':O}Mtr') if (beamline=="amx"): @@ -203,6 +203,7 @@ class SampleXYZ(Device): back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") back_light_range = (back_light_low_limit.get(), back_light_high_limit.get()) + samplexyz = SampleXYZ("XF:19IDC-ES{Gon:1-Ax", name="samplexyz") else: raise Exception(f"Invalid beamline name provided: {beamline}") From 11f8dfa68d0bb7a9655e2d2dba94723757d50965 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 07:43:37 -0700 Subject: [PATCH 036/234] Compliance merge for GUI split --- gui/control_main.py | 219 ++++++++++++++++++++++++++++++-------------- 1 file changed, 152 insertions(+), 67 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index fac940d7..842e8a98 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -379,6 +379,8 @@ def createSampleTab(self): self.closeShutterButton = QtWidgets.QPushButton("Close Photon Shutter") self.closeShutterButton.clicked.connect(self.closePhotonShutterCB) self.closeShutterButton.setStyleSheet("background-color: red") + self.parkRobotButton = QtWidgets.QPushButton("Park Robot") + self.parkRobotButton.clicked.connect(self.parkRobotCB) hBoxTreeButtsLayout = QtWidgets.QHBoxLayout() vBoxTreeButtsLayoutLeft = QtWidgets.QVBoxLayout() vBoxTreeButtsLayoutRight = QtWidgets.QVBoxLayout() @@ -389,6 +391,7 @@ def createSampleTab(self): vBoxTreeButtsLayoutLeft.addWidget(self.popUserScreen) vBoxTreeButtsLayoutLeft.addWidget(warmupButton) vBoxTreeButtsLayoutRight.addWidget(self.closeShutterButton) + vBoxTreeButtsLayoutRight.addWidget(self.parkRobotButton) vBoxTreeButtsLayoutRight.addWidget(unmountSampleButton) vBoxTreeButtsLayoutRight.addWidget(deQueueSelectedButton) vBoxTreeButtsLayoutRight.addWidget(emptyQueueButton) @@ -430,10 +433,10 @@ def createSampleTab(self): ) if daq_utils.beamline == "fmx": self.osc_end_ledit.textChanged.connect(self.calcLifetimeCB) - hBoxColParams1.addWidget(colStartLabel) - hBoxColParams1.addWidget(self.osc_start_ledit) - hBoxColParams1.addWidget(self.colEndLabel) - hBoxColParams1.addWidget(self.osc_end_ledit) + #hBoxColParams1.addWidget(colStartLabel) + #hBoxColParams1.addWidget(self.osc_start_ledit) + #hBoxColParams1.addWidget(self.colEndLabel) + #hBoxColParams1.addWidget(self.osc_end_ledit) hBoxColParams2 = QtWidgets.QHBoxLayout() colRangeLabel = QtWidgets.QLabel("Oscillation Width:") colRangeLabel.setFixedWidth(140) @@ -468,13 +471,13 @@ def createSampleTab(self): ) ) self.exp_time_ledit.textChanged.connect(self.checkEntryState) - hBoxColParams2.addWidget(colRangeLabel) - hBoxColParams2.addWidget(self.osc_range_ledit) + #hBoxColParams2.addWidget(colRangeLabel) + #hBoxColParams2.addWidget(self.osc_range_ledit) - hBoxColParams2.addWidget(colExptimeLabel) - hBoxColParams2.addWidget(self.exp_time_ledit) + #hBoxColParams2.addWidget(colExptimeLabel) + #hBoxColParams2.addWidget(self.exp_time_ledit) hBoxColParams25 = QtWidgets.QHBoxLayout() - hBoxColParams25.addWidget(self.stillModeCheckBox) + #hBoxColParams25.addWidget(self.stillModeCheckBox) totalExptimeLabel = QtWidgets.QLabel("Total Exposure Time (s):") totalExptimeLabel.setFixedWidth(155) totalExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) @@ -502,12 +505,12 @@ def createSampleTab(self): calcLifetimeButton.clicked.connect(self.calcLifetimeCB) self.sampleLifetimeReadback_ledit = QtWidgets.QLabel() self.calcLifetimeCB() - hBoxColParams25.addWidget(totalExptimeLabel) - hBoxColParams25.addWidget(self.totalExptime_ledit) + #hBoxColParams25.addWidget(totalExptimeLabel) + #hBoxColParams25.addWidget(self.totalExptime_ledit) # if (daq_utils.beamline == "fmx"): # hBoxColParams25.addWidget(calcLifetimeButton) - hBoxColParams25.addWidget(sampleLifetimeLabel) - hBoxColParams25.addWidget(self.sampleLifetimeReadback_ledit) + #hBoxColParams25.addWidget(sampleLifetimeLabel) + #hBoxColParams25.addWidget(self.sampleLifetimeReadback_ledit) hBoxColParams22 = QtWidgets.QHBoxLayout() if daq_utils.beamline in ("fmx", "nyx"): if getBlConfig("attenType") == "RI": @@ -578,35 +581,30 @@ def createSampleTab(self): self.energy_ledit.returnPressed.connect(self.moveEnergyMaxDeltaCB) moveEnergyButton = QtWidgets.QPushButton("Move Energy") moveEnergyButton.clicked.connect(self.moveEnergyCB) - hBoxColParams3.addWidget(colEnergyLabel) - hBoxColParams3.addWidget(self.energyReadback) - hBoxColParams3.addWidget(energySPLabel) - if daq_utils.beamline == "fmx": - if getBlConfig(SET_ENERGY_CHECK): - hBoxColParams3.addWidget(moveEnergyButton) - else: - hBoxColParams3.addWidget(self.energy_ledit) - else: - hBoxColParams3.addWidget(self.energy_ledit) - - hBoxColParams22.addWidget(colTransmissionLabel) - hBoxColParams22.addWidget(self.transmissionReadback_ledit) - hBoxColParams22.addWidget(transmisionSPLabel) - hBoxColParams22.addWidget(self.transmission_ledit) - hBoxColParams22.insertSpacing(5, 100) - hBoxColParams22.addWidget(beamsizeLabel) - hBoxColParams22.addWidget(self.beamsizeComboBox) + #hBoxColParams3.addWidget(colEnergyLabel) + #hBoxColParams3.addWidget(self.energyReadback) + #hBoxColParams3.addWidget(energySPLabel) + #hBoxColParams3.addWidget(self.energy_ledit) + #hBoxColParams22.addWidget(colTransmissionLabel) + #hBoxColParams22.addWidget(self.transmissionReadback_ledit) + #hBoxColParams22.addWidget(transmisionSPLabel) + #hBoxColParams22.addWidget(self.transmission_ledit) + #hBoxColParams22.insertSpacing(5, 100) + #hBoxColParams22.addWidget(beamsizeLabel) + #hBoxColParams22.addWidget(self.beamsizeComboBox) hBoxColParams4 = QtWidgets.QHBoxLayout() colBeamWLabel = QtWidgets.QLabel("Beam Width:") colBeamWLabel.setFixedWidth(140) colBeamWLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamWidth_ledit = QtWidgets.QLineEdit() self.beamWidth_ledit.setFixedWidth(60) + self.beamWidth_ledit.setText(getBlConfig("screen_default_beamWidth")) colBeamHLabel = QtWidgets.QLabel("Beam Height:") colBeamHLabel.setFixedWidth(140) colBeamHLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamHeight_ledit = QtWidgets.QLineEdit() self.beamHeight_ledit.setFixedWidth(60) + self.beamHeight_ledit.setText(getBlConfig("screen_default_beamHeight")) hBoxColParams4.addWidget(colBeamWLabel) hBoxColParams4.addWidget(self.beamWidth_ledit) hBoxColParams4.addWidget(colBeamHLabel) @@ -621,7 +619,7 @@ def createSampleTab(self): if daq_utils.beamline == "nyx": self.resolution_ledit.setEnabled(False) detDistLabel = QtWidgets.QLabel("Detector Dist.") - detDistLabel.setAlignment(QtCore.Qt.AlignCenter) + #detDistLabel.setAlignment(QtCore.Qt.AlignCenter) detDistRBLabel = QtWidgets.QLabel("Readback:") self.detDistRBVLabel = QtEpicsPVLabel( daq_utils.motor_dict["detectorDist"] + ".RBV", self, 70 @@ -645,10 +643,10 @@ def createSampleTab(self): self.detDistMotorEntry.getEntry().returnPressed.connect(self.moveDetDistCB) self.moveDetDistButton = QtWidgets.QPushButton("Move Detector") self.moveDetDistButton.clicked.connect(self.moveDetDistCB) - hBoxColParams3.addWidget(detDistLabel) - hBoxColParams3.addWidget(self.detDistRBVLabel.getEntry()) - hBoxColParams3.addWidget(detDistSPLabel) - hBoxColParams3.addWidget(self.detDistMotorEntry.getEntry()) + #hBoxColParams3.addWidget(detDistLabel) + #hBoxColParams3.addWidget(self.detDistRBVLabel.getEntry()) + #hBoxColParams3.addWidget(detDistSPLabel) + #hBoxColParams3.addWidget(self.detDistMotorEntry.getEntry()) hBoxColParams6 = QtWidgets.QHBoxLayout() hBoxColParams6.setAlignment(QtCore.Qt.AlignLeft) hBoxColParams7 = QtWidgets.QHBoxLayout() @@ -692,18 +690,22 @@ def createSampleTab(self): self.protoOtherRadio = QtWidgets.QRadioButton("other") self.protoOtherRadio.setEnabled(False) self.protoRadioGroup.addButton(self.protoOtherRadio) - protoOptionList = [ - "standard", - "raster", - "vector", - "burn", - "rasterScreen", - "stepRaster", - "stepVector", - "multiCol", - "characterize", - "ednaCol", - ] # these should probably come from db + if daq_utils.beamline == "nyx": + protoOptionList = ["standard","raster","vector"] # these should probably come from db + + else: + protoOptionList = [ + "standard", + "raster", + "vector", + "burn", + "rasterScreen", + "stepRaster", + "stepVector", + "multiCol", + "characterize", + "ednaCol", + ] # these should probably come from db self.protoComboBox = QtWidgets.QComboBox(self) self.protoComboBox.addItems(protoOptionList) self.protoComboBox.activated[str].connect(self.protoComboActivatedCB) @@ -714,8 +716,8 @@ def createSampleTab(self): hBoxColParams6.addWidget(self.protoComboBox) hBoxColParams7.addWidget(centeringLabel) hBoxColParams7.addWidget(self.centeringComboBox) - hBoxColParams7.addWidget(colResoLabel) - hBoxColParams7.addWidget(self.resolution_ledit) + #hBoxColParams7.addWidget(colResoLabel) + #hBoxColParams7.addWidget(self.resolution_ledit) self.processingOptionsFrame = QFrame() self.hBoxProcessingLayout1 = QtWidgets.QHBoxLayout() self.hBoxProcessingLayout1.setAlignment(QtCore.Qt.AlignLeft) @@ -894,19 +896,98 @@ def createSampleTab(self): vBoxColParams1.addLayout(hBoxColParams25) vBoxColParams1.addLayout(hBoxColParams22) vBoxColParams1.addLayout(hBoxColParams3) - vBoxColParams1.addLayout(hBoxColParams7) - vBoxColParams1.addLayout(hBoxColParams6) - vBoxColParams1.addWidget(self.rasterParamsFrame) - vBoxColParams1.addWidget(self.multiColParamsFrame) - vBoxColParams1.addWidget(self.vectorParamsFrame) - vBoxColParams1.addWidget(self.characterizeParamsFrame) - vBoxColParams1.addWidget(self.processingOptionsFrame) + #vBoxColParams1.addLayout(hBoxColParams7) + #vBoxColParams1.addLayout(hBoxColParams6) + #vBoxColParams1.addWidget(self.rasterParamsFrame) + #vBoxColParams1.addWidget(self.multiColParamsFrame) + #vBoxColParams1.addWidget(self.vectorParamsFrame) + #vBoxColParams1.addWidget(self.characterizeParamsFrame) + #vBoxColParams1.addWidget(self.processingOptionsFrame) + mikesGB = QtWidgets.QGroupBox() + mikesGB.setTitle("Acquisition") + + paramSubspace = QGridLayout() + + #paramSubspace.setColumnMinimumWidth(0, 140) + #paramSubspace.setColumnMinimumWidth(1, 90) + #paramSubspace.setColumnMinimumWidth(2, 140) + #paramSubspace.setColumnMinimumWidth(3, 90) + #paramSubspace.setColumnMinimumWidth(4, 90) + #paramSubspace.setColumnStretch(1) learn about stretch factor and then update + + + # Parameter Collection Column 1, Labels + colStartLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(colStartLabel,1,0, alignment=QtCore.Qt.AlignLeft) + self.colEndLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.colEndLabel,2,0, alignment=QtCore.Qt.AlignLeft) + colRangeLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(colRangeLabel,0,0, alignment=QtCore.Qt.AlignLeft) + colExptimeLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(colExptimeLabel,3,0, alignment=QtCore.Qt.AlignLeft) + totalExptimeLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(totalExptimeLabel,4,0, alignment=QtCore.Qt.AlignLeft) + # Parameter Collection Column 2, Input Boxes + paramSubspace.addWidget(self.osc_start_ledit,1,1, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.osc_end_ledit,2,1, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.osc_range_ledit,0,1, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.exp_time_ledit,3,1, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.totalExptime_ledit,4,1, alignment=QtCore.Qt.AlignLeft) + # Parameter Collection Column 3, Labels + paramSubspace.addWidget(detDistLabel,0,2, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(colResoLabel,1,2, alignment=QtCore.Qt.AlignLeft) + #paramSubspace.addWidget(detDistSPLabel,1,2, alignment=QtCore.Qt.AlignLeft) + #hBoxColParams7.addWidget(colResoLabel) + paramSubspace.addWidget(colEnergyLabel,2,2, alignment=QtCore.Qt.AlignLeft) + #paramSubspace.addWidget(energySPLabel,3,2, alignment=QtCore.Qt.AlignLeft) + colTransmissionLabel.setAlignment(QtCore.Qt.AlignLeft) + paramSubspace.addWidget(colTransmissionLabel,3,2, alignment=QtCore.Qt.AlignLeft) + transmisionSPLabel.setAlignment(QtCore.Qt.AlignLeft) + #paramSubspace.addWidget(transmisionSPLabel,3,2, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(beamsizeLabel,4,2, alignment=QtCore.Qt.AlignLeft) + # Parameter Collection Column 4, Input Boxes + paramSubspace.addWidget(self.detDistMotorEntry.getEntry(),0,3, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.resolution_ledit,1,3, alignment=QtCore.Qt.AlignLeft) + #hBoxColParams7.addWidget(self.resolution_ledit) + if daq_utils.beamline == "fmx": + if getBlConfig(SET_ENERGY_CHECK): + paramSubspace.addWidget(moveEnergyButton,2,3, alignment=QtCore.Qt.AlignLeft) + else: + paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) + else: + paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) + + paramSubspace.addWidget(self.transmission_ledit,3,3, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.beamsizeComboBox,4,3, alignment=QtCore.Qt.AlignLeft) + # Param Collection Column 5, RBV + paramSubspace.addWidget(self.energyReadback,2,4, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.detDistRBVLabel.getEntry(),0,4, alignment=QtCore.Qt.AlignLeft) + paramSubspace.addWidget(self.transmissionReadback_ledit,3,4, alignment=QtCore.Qt.AlignLeft) + + improvedParamSpacing = QVBoxLayout() + improvedParamSpacing.addWidget(self.stillModeCheckBox) + improvedParamSpacing.addLayout(paramSubspace) + improvedParamSpacing.addLayout(hBoxColParams7) + improvedParamSpacing.addLayout(hBoxColParams6) + improvedParamSpacing.addWidget(self.rasterParamsFrame) + improvedParamSpacing.addWidget(self.multiColParamsFrame) + improvedParamSpacing.addWidget(self.vectorParamsFrame) + improvedParamSpacing.addWidget(self.characterizeParamsFrame) + improvedParamSpacing.addWidget(self.processingOptionsFrame) + mikesGB.setLayout(improvedParamSpacing) + self.rasterParamsFrame.hide() self.multiColParamsFrame.hide() self.characterizeParamsFrame.hide() colParamsGB.setLayout(vBoxColParams1) self.dataPathGB = DataLocInfo(self) - vBoxMainColLayout.addWidget(colParamsGB) + hBoxDisplayOptionLayout= QtWidgets.QHBoxLayout() + self.albulaDispCheckBox = QCheckBox("Display Data (Albula)") + self.albulaDispCheckBox.setChecked(False) + hBoxDisplayOptionLayout.addWidget(self.albulaDispCheckBox) + #vBoxMainColLayout.addWidget(colParamsGB) + vBoxMainColLayout.addWidget(mikesGB) + vBoxMainColLayout.addWidget(self.dataPathGB) self.mainColFrame.setLayout(vBoxMainColLayout) self.mainToolBox.addItem(self.mainColFrame, "Collection Parameters") @@ -1240,6 +1321,7 @@ def createSampleTab(self): self.vidActionRasterSelectRadio = QtWidgets.QRadioButton("Raster Select") self.vidActionRasterSelectRadio.setChecked(False) self.vidActionRasterSelectRadio.toggled.connect(self.vidActionToggledCB) + self.vidActionRadioGroup.addButton(self.vidActionRasterSelectRadio) self.vidActionRasterDefRadio = QtWidgets.QRadioButton("Define Raster") self.vidActionRasterDefRadio.setChecked(False) self.vidActionRasterDefRadio.setEnabled(False) @@ -1429,15 +1511,15 @@ def createSampleTab(self): self.centeringComboBox.setDisabled(True) self.beamsizeComboBox.setDisabled(True) annealButton.setDisabled(True) - centerLoopButton.setDisabled(True) + centerLoopButton.setEnabled(True) clearGraphicsButton.setDisabled(True) saveCenteringButton.setDisabled(True) selectAllCenteringButton.setDisabled(True) snapshotButton.setDisabled(True) - self.hideRastersCheckBox.setDisabled(True) - self.vidActionC2CRadio.setDisabled(True) - self.vidActionRasterExploreRadio.setDisabled(True) - self.vidActionRasterDefRadio.setDisabled(True) + self.hideRastersCheckBox.setEnabled(True) + self.vidActionC2CRadio.setEnabled(True) + self.vidActionRasterExploreRadio.setEnabled(True) + self.vidActionRasterDefRadio.setEnabled(True) self.vidActionDefineCenterRadio.setDisabled(True) self.hutchCornerCamThread = VideoThread( @@ -3273,22 +3355,22 @@ def getCurrentFOV(self): def screenXPixels2microns(self, pixels): fov = self.getCurrentFOV() - fovX = fov["x"] + fovX = fov["x"]*1000 return float(pixels) * (fovX / daq_utils.screenPixX) def screenYPixels2microns(self, pixels): fov = self.getCurrentFOV() - fovY = fov["y"] + fovY = fov["y"]*1000 return float(pixels) * (fovY / daq_utils.screenPixY) def screenXmicrons2pixels(self, microns): fov = self.getCurrentFOV() - fovX = fov["x"] + fovX = fov["x"]*1000 return int(round(microns * (daq_utils.screenPixX / fovX))) def screenYmicrons2pixels(self, microns): fov = self.getCurrentFOV() - fovY = fov["y"] + fovY = fov["y"]*1000 return int(round(microns * (daq_utils.screenPixY / fovY))) def definePolyRaster( @@ -4231,6 +4313,9 @@ def popUserScreenCB(self): else: self.popupServerMessage("You don't have control") + def parkRobotCB(self): + self.send_to_server("parkRobot()") + def closePhotonShutterCB(self): self.photonShutterClose_pv.put(1) From 287005936819eded0ba3321e94ac4bd151b2b4d0 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 10:50:05 -0400 Subject: [PATCH 037/234] temporarily disabling loop center button --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 842e8a98..49b4fdb6 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1511,7 +1511,7 @@ def createSampleTab(self): self.centeringComboBox.setDisabled(True) self.beamsizeComboBox.setDisabled(True) annealButton.setDisabled(True) - centerLoopButton.setEnabled(True) + centerLoopButton.setDisabled(True) clearGraphicsButton.setDisabled(True) saveCenteringButton.setDisabled(True) selectAllCenteringButton.setDisabled(True) From 4b4e5cc8a9f08bae463f1e02350c113f8e98365b Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 10:54:51 -0400 Subject: [PATCH 038/234] nyxtools overlay update --- bin/lsdcServer_nyx.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lsdcServer_nyx.cmd b/bin/lsdcServer_nyx.cmd index 47fe7b11..a6aaf719 100755 --- a/bin/lsdcServer_nyx.cmd +++ b/bin/lsdcServer_nyx.cmd @@ -3,7 +3,7 @@ export PROJDIR=/nsls2/software/mx/daq/ export CONFIGDIR=${PROJDIR}bnlpx_config/ export LSDCHOME=${PROJDIR}lsdc_nyx #export EPICS_CA_AUTO_ADDR_LIST=NO -export PYTHONPATH=".:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" +export PYTHONPATH="/nsls2/data/nyx/shared/config/lsdc_overlay/lsdc-server-2023-2.0-ex-raster/lib/python3.8/site-packages:.:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" export PATH=/usr/local/bin:/usr/bin:/bin:${PROJDIR}/software/bin:/opt/ccp4/bin source ${CONFIGDIR}daq_env_nyx.txt export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib From 9115ffb1d78bfcd1d3402e189117202945b4f14b Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 13 Jul 2023 11:18:53 -0400 Subject: [PATCH 039/234] using bps.sleep(...) in plan, thanks to tacaswell --- daq_macros.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 637d3f57..845eb4ca 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -2067,7 +2067,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): yield from zebraDaqRasterBluesky(raster_flyer, omega, numsteps, img_width_per_cell * numsteps, img_width_per_cell, exptimePerCell, rasterFilePrefix, data_directory_name, file_number_start, row_index, vector) #raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row - time.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4 + yield from bps.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4 # processing if (procFlag): if (daq_utils.detector_id == "EIGER-16"): @@ -2091,7 +2091,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): initiate transitions here allows for GUI sample/heat map image to update after moving to known position""" logger.debug(f'lastOnSample(): {lastOnSample()} autoRasterFlag: {autoRasterFlag}') - time.sleep(3) #waiting for detector to not lose row + yield from bps.sleep(3) #waiting for detector to not lose row if (lastOnSample() and not autoRasterFlag): govStatus = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) if govStatus.exception(): From 786b75b350857ac6849088b9a23d7e58a96a8f87 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 13 Jul 2023 11:30:42 -0400 Subject: [PATCH 040/234] more changes based on #314 review --- daq_macros.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 845eb4ca..7e91dafb 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -2060,10 +2060,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): logger.info(f'starting new row: {row_index}') zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} - yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000) - yield from bps.mv(samplexyz.y, yMotAbsoluteMove/1000) - yield from bps.mv(samplexyz.z, zMotAbsoluteMove/1000) - yield from bps.mv(samplexyz.omega, omega-0.05) + yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) yield from zebraDaqRasterBluesky(raster_flyer, omega, numsteps, img_width_per_cell * numsteps, img_width_per_cell, exptimePerCell, rasterFilePrefix, data_directory_name, file_number_start, row_index, vector) #raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row @@ -3572,7 +3569,7 @@ def xrec_no_zebra(angle_start): logger.info("xnz: read") image_data = response.read() logger.info("xnz: read") - with open("findloop_"+str(omega_target/40)+".jpg", "wb") as filename: + with open(getBlConfig("visitDirectory")+"findloop_"+str(omega_target/40)+".jpg", "wb") as filename: logger.info("xnz: write file") filename.write(image_data) logger.info("xnz: write file") From 5afcdd4b720783115d59834bd1d1053e0edab104 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 13 Jul 2023 11:34:44 -0400 Subject: [PATCH 041/234] removed useless line preservation --- daq_macros.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 7e91dafb..504ff060 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -2051,8 +2051,6 @@ def snakeRasterBluesky(rasterReqID, grain=""): if govStatus.exception(): logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}") return - #raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) - #raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement From 5ea093945d3d0a909f69a9413d45efaea9c50a1a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 13 Jul 2023 11:40:56 -0400 Subject: [PATCH 042/234] now using setPvDesc alias * thank you jun for this suggestion --- daq_macros.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 504ff060..6dd7965a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3559,9 +3559,9 @@ def xrec_no_zebra(angle_start): logger.info(f'taking image at {omega_target}') timeout = 5 # change image mode to single(0) - beamline_support.setPvValFromDescriptor("lowMagImMode",0) + setPvDesc("lowMagImMode",0) # start camera - beamline_support.setPvValFromDescriptor("lowMagAcquire",1) + setPvDesc("lowMagAcquire",1) try: with urllib.request.urlopen(daq_utils.lowMagCamURL, timeout=timeout) as response: logger.info("xnz: read") @@ -3576,9 +3576,9 @@ def xrec_no_zebra(angle_start): logger.info("xnz: sleep") time.sleep(1) # change image mode to continuous(2) - beamline_support.setPvValFromDescriptor("lowMagImMode",2) + setPvDesc("lowMagImMode",2) # start camera - beamline_support.setPvValFromDescriptor("lowMagAcquire",1) + setPvDesc("lowMagAcquire",1) def zebraCamDaq(angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega From 5d8c51ba14df3074a98f72ae68c2fe048742e7d7 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 14 Jul 2023 11:44:56 -0400 Subject: [PATCH 043/234] Update daq_macros.py Co-authored-by: Thomas A Caswell --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 6dd7965a..67fffe0d 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3567,7 +3567,7 @@ def xrec_no_zebra(angle_start): logger.info("xnz: read") image_data = response.read() logger.info("xnz: read") - with open(getBlConfig("visitDirectory")+"findloop_"+str(omega_target/40)+".jpg", "wb") as filename: + with open(getBlConfig("visitDirectory")+"findloop_"+str(omega_target//40)+".jpg", "wb") as filename: logger.info("xnz: write file") filename.write(image_data) logger.info("xnz: write file") From cbec04c74399fc64147c7d8bad023bada0dd1151 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 12:14:41 -0400 Subject: [PATCH 044/234] removes /var/log/dama/ log writing --- utils/healthcheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/healthcheck.py b/utils/healthcheck.py index 0e8bd601..c21939c5 100644 --- a/utils/healthcheck.py +++ b/utils/healthcheck.py @@ -75,7 +75,7 @@ def check_working_directory(): check_working_directory.remediation = f'Please start LSDC in {daq_utils.beamline} data directory. Current directory: {working_dir}' return False if daq_utils.getBlConfig("visitDirectory") != os.getcwd(): - check_working_directory.remediation = ("Working directory mismatch. Please start LSDC GUI in the same folder as the server is running.") + check_working_directory.remediation = (f"Working directory mismatch ({daq_utils.getBlConfig('visitDirectory')}!={os.getcwd()}). Please start LSDC GUI in the same folder as the server is running.") return False return True From 68674f018aaaeffaac1e1742d1fe7aa7bf3b7778 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 14:30:36 -0400 Subject: [PATCH 045/234] missing imports --- gui/dialog/staff_screen.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 8460ff3b..afbb21c7 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -1,5 +1,6 @@ import logging import typing +import daq_utils from qtpy import QtCore, QtWidgets from qtpy.QtWidgets import QCheckBox From 335e3752d91bd26662c6c138d1b6acf3160f6d1b Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 14:31:10 -0400 Subject: [PATCH 046/234] fixed bad package declaration --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 49b4fdb6..cb2cfd68 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -906,7 +906,7 @@ def createSampleTab(self): mikesGB = QtWidgets.QGroupBox() mikesGB.setTitle("Acquisition") - paramSubspace = QGridLayout() + paramSubspace = QtWidgets.QGridLayout() #paramSubspace.setColumnMinimumWidth(0, 140) #paramSubspace.setColumnMinimumWidth(1, 90) @@ -964,7 +964,7 @@ def createSampleTab(self): paramSubspace.addWidget(self.detDistRBVLabel.getEntry(),0,4, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(self.transmissionReadback_ledit,3,4, alignment=QtCore.Qt.AlignLeft) - improvedParamSpacing = QVBoxLayout() + improvedParamSpacing = QtWidgets.QVBoxLayout() improvedParamSpacing.addWidget(self.stillModeCheckBox) improvedParamSpacing.addLayout(paramSubspace) improvedParamSpacing.addLayout(hBoxColParams7) From 9f99f8c31725de470dec772bb28e626e9787d23f Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 21 Jun 2023 14:31:43 -0400 Subject: [PATCH 047/234] remove /var/log/dama logging --- daq_main2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daq_main2.py b/daq_main2.py index 51812ce1..752f67a3 100755 --- a/daq_main2.py +++ b/daq_main2.py @@ -28,12 +28,12 @@ logging.getLogger('ophyd').setLevel(logging.WARN) logging.getLogger('caproto').setLevel(logging.WARN) handler1 = handlers.RotatingFileHandler('lsdcServerLog.txt', maxBytes=5000000, backupCount=100) -handler2 = handlers.RotatingFileHandler('/var/log/dama/%slsdcServerLog.txt' % os.environ['BEAMLINE_ID'], maxBytes=5000000, backupCount=100) +#handler2 = handlers.RotatingFileHandler('/var/log/dama/%slsdcServerLog.txt' % os.environ['BEAMLINE_ID'], maxBytes=5000000, backupCount=100) myformat = logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s') handler1.setFormatter(myformat) -handler2.setFormatter(myformat) +#handler2.setFormatter(myformat) logger.addHandler(handler1) -logger.addHandler(handler2) +#logger.addHandler(handler2) perform_server_checks() setBlConfig("visitDirectory", os.getcwd()) From 34eb0b9fcd739f5f9e205ece24dfe78cb7255539 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 22 Jun 2023 09:49:42 -0400 Subject: [PATCH 048/234] changes to meet compliance testing --- daq_macros.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 67fffe0d..34488dae 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -2012,7 +2012,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): setPvDesc("sampleProtect",0) setPvDesc("vectorGo", 0) #set to 0 to allow easier camonitoring vectorGo data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, omega, rasterStartX, rasterStartY, rasterStartZ, omegaRad, rowCount, numsteps, totalImages, rows = params_from_raster_req_id(rasterReqID) - rasterRowResultsList = [{} for i in range(0,rowCount)] + rasterRowResultsList = [{} for i in range(0,rowCount)] processedRasterRowCount = 0 rasterEncoderMap = {} @@ -2029,31 +2029,27 @@ def snakeRasterBluesky(rasterReqID, grain=""): parentRequest = db_lib.getRequestByID(parentReqID) parentReqObj = parentRequest["request_obj"] parentReqProtocol = parentReqObj["protocol"] - detDist = parentReqObj["detDist"] + detDist = parentReqObj["detDist"] rasterFilePrefix = dataFilePrefix + "_Raster" total_exposure_time = exptimePerCell*totalImages detDist /= 1000 # TODO find a way to standardize detector distance settings + raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) if raster_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' 'this collection, retry when ready.') + raster_flyer.detector.cam.acquire.put(0) logger.warning("Detector was in the armed state prior to this attempted collection.") return 0 - start_time = time.time() - arm_status = raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix, + raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix, data_directory_name=data_directory_name, file_number_start=file_number_start, x_beam=xbeam, y_beam=ybeam, wavelength=wave, det_distance_m=detDist, - num_images_per_file=numsteps) - govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait() - logger.info(f"Governor move to DA and synchronous arming took {time.time() - start_time} seconds.") - if govStatus.exception(): - logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}") - return + num_images_per_file=numsteps) # rasterDef['numCells']) TODO: try to get all images in one file + #raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] - yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement + yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movemen for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row logger.info(f'starting new row: {row_index}') zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) @@ -2063,8 +2059,9 @@ def snakeRasterBluesky(rasterReqID, grain=""): data_directory_name, file_number_start, row_index, vector) #raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row yield from bps.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4 + # processing - if (procFlag): + if (procFlag): if (daq_utils.detector_id == "EIGER-16"): seqNum = int(raster_flyer.detector.file.sequence_id.get()) else: @@ -2110,7 +2107,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): #data acquisition is finished, now processing and sample positioning if not procFlag: # no, no processing. just move to raster start #must go to known position to account for windup dist. - logger.info("moving to raster start") + logger.info(f" no processing! moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}") yield from bps.mv(samplexyz.x, rasterStartX) yield from bps.mv(samplexyz.y, rasterStartY) yield from bps.mv(samplexyz.z, rasterStartZ) @@ -2123,16 +2120,16 @@ def snakeRasterBluesky(rasterReqID, grain=""): [thread.join(timeout=120) for thread in spotFindThreadList] else: logger.info("raster aborted, do not wait for spotfind threads") - logger.info(str(processedRasterRowCount) + "/" + str(rowCount)) + logger.info(str(processedRasterRowCount) + "/" + str(rowCount)) rasterResult = generateGridMap(rasterRequest) - + logger.info(f'protocol = {reqObj["protocol"]}') if (reqObj["protocol"] == "multiCol" or parentReqProtocol == "multiColQ"): - if (parentReqProtocol == "multiColQ"): + if (parentReqProtocol == "multiColQ"): multiColThreshold = parentReqObj["diffCutoff"] else: - multiColThreshold = reqObj["diffCutoff"] - #gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) + multiColThreshold = reqObj["diffCutoff"] + # gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}") yield from bps.mv(samplexyz.x, rasterStartX) yield from bps.mv(samplexyz.y, rasterStartY) @@ -2168,7 +2165,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): rasterRequest["request_obj"]["rasterDef"]["status"] = ( RasterStatus.READY_FOR_SNAPSHOT.value ) - db_lib.updateRequest(rasterRequest) + db_lib.updateRequest(rasterRequest) db_lib.updatePriority(rasterRequestID,-1) #ensure gov transitions have completed successfully @@ -3696,9 +3693,10 @@ def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposur 'change_state':changeState, 'transmission':vector_params["transmission"], 'data_path':data_path} start_time = time.time() - arm_status = flyer.detector_arm(**required_parameters) + #flyer.detector_arm(**required_parameters) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait() + #arm_status.wait() + time.sleep(0.5) logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") From b600f7aa8b1175509f67d0e9e890262c4c9516de Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 14 Jul 2023 14:24:58 -0400 Subject: [PATCH 049/234] removed commented code --- daq_main2.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/daq_main2.py b/daq_main2.py index 752f67a3..6cf2da58 100755 --- a/daq_main2.py +++ b/daq_main2.py @@ -28,12 +28,9 @@ logging.getLogger('ophyd').setLevel(logging.WARN) logging.getLogger('caproto').setLevel(logging.WARN) handler1 = handlers.RotatingFileHandler('lsdcServerLog.txt', maxBytes=5000000, backupCount=100) -#handler2 = handlers.RotatingFileHandler('/var/log/dama/%slsdcServerLog.txt' % os.environ['BEAMLINE_ID'], maxBytes=5000000, backupCount=100) myformat = logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s') handler1.setFormatter(myformat) -#handler2.setFormatter(myformat) logger.addHandler(handler1) -#logger.addHandler(handler2) perform_server_checks() setBlConfig("visitDirectory", os.getcwd()) From d452c7deb26749b289652651e242b0826c279c4b Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 14 Jul 2023 14:26:21 -0400 Subject: [PATCH 050/234] t --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 34488dae..20d8fc12 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -2049,7 +2049,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): #raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] - yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movemen + yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row logger.info(f'starting new row: {row_index}') zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) From aa2c204fbb1c0684b5e3da9654c2d4548a379847 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 14 Jul 2023 16:20:18 -0400 Subject: [PATCH 051/234] remove overlay from pythonpath --- bin/lsdcServer_nyx.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lsdcServer_nyx.cmd b/bin/lsdcServer_nyx.cmd index a6aaf719..47fe7b11 100755 --- a/bin/lsdcServer_nyx.cmd +++ b/bin/lsdcServer_nyx.cmd @@ -3,7 +3,7 @@ export PROJDIR=/nsls2/software/mx/daq/ export CONFIGDIR=${PROJDIR}bnlpx_config/ export LSDCHOME=${PROJDIR}lsdc_nyx #export EPICS_CA_AUTO_ADDR_LIST=NO -export PYTHONPATH="/nsls2/data/nyx/shared/config/lsdc_overlay/lsdc-server-2023-2.0-ex-raster/lib/python3.8/site-packages:.:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" +export PYTHONPATH=".:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" export PATH=/usr/local/bin:/usr/bin:/bin:${PROJDIR}/software/bin:/opt/ccp4/bin source ${CONFIGDIR}daq_env_nyx.txt export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib From 3b752c6a9edb5c35da3912ce25344132415c454a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 18 Jul 2023 07:58:10 -0400 Subject: [PATCH 052/234] Hiding the launch directory --- utils/healthcheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/healthcheck.py b/utils/healthcheck.py index c21939c5..0120b711 100644 --- a/utils/healthcheck.py +++ b/utils/healthcheck.py @@ -75,7 +75,7 @@ def check_working_directory(): check_working_directory.remediation = f'Please start LSDC in {daq_utils.beamline} data directory. Current directory: {working_dir}' return False if daq_utils.getBlConfig("visitDirectory") != os.getcwd(): - check_working_directory.remediation = (f"Working directory mismatch ({daq_utils.getBlConfig('visitDirectory')}!={os.getcwd()}). Please start LSDC GUI in the same folder as the server is running.") + check_working_directory.remediation = (f"Working directory mismatch. Please start LSDC GUI in the same folder as the server is running.") return False return True From d1fba09eea4e2118e714d4f4b9242431475975f9 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 27 Jul 2023 18:43:51 -0400 Subject: [PATCH 053/234] Fixed missing detector arming in standard collection --- daq_macros.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 20d8fc12..cb777541 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -29,6 +29,7 @@ from collections import OrderedDict from threading import Thread from config_params import * +from ophyd.status import SubscriptionStatus from kafka_producer import send_kafka_message import gov_lib @@ -3693,10 +3694,21 @@ def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposur 'change_state':changeState, 'transmission':vector_params["transmission"], 'data_path':data_path} start_time = time.time() - #flyer.detector_arm(**required_parameters) + flyer.detector_arm(**required_parameters) + + def armed_callback(value, old_value, **kwargs): + if old_value == 0 and value == 1: + return True + return False + + arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) + + flyer.detector.cam.acquire.put(1) + govStatus = gov_lib.setGovRobot(gov_robot, "DA") - #arm_status.wait() time.sleep(0.5) + govStatus.wait() + arm_status.wait() logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") From 9be9a1ccd0fbd1173bcf8659082b940ad84ebbff Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 24 Aug 2023 13:36:27 -0400 Subject: [PATCH 054/234] ophydizing omega axis --- gon_lib.py | 8 +++++++- start_bs.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/gon_lib.py b/gon_lib.py index ce0f5d28..70f58d85 100755 --- a/gon_lib.py +++ b/gon_lib.py @@ -2,11 +2,17 @@ import beamline_lib import beamline_support from beamline_support import getPvValFromDescriptor as getPvDesc, setPvValFromDescriptor as setPvDesc -from start_bs import back_light, back_light_range +from start_bs import back_light, back_light_range, md2 import logging logger = logging.getLogger(__name__) BACK_LIGHT_STEP = 0.05 # percent of intensity range +def omegaMoveAbs(angle): + md2.omega.set(angle) + +def omegaMoveRel(angle): + md2.omega.set(md2.omega.get() + angle) + def backlightBrighter(): intensity = back_light.get() intensity += BACK_LIGHT_STEP * (back_light_range[1]-back_light_range[0]) diff --git a/start_bs.py b/start_bs.py index 17901213..75b3776d 100755 --- a/start_bs.py +++ b/start_bs.py @@ -87,6 +87,15 @@ class ABBIXMercury(Mercury1, SoftDXPTrigger): pass + +class MD2Device(Device): + omega = Cpt(EpicsSignal, 'MD2S:OmegaPosition',name='omega') + x = Cpt(EpicsSignal, 'MD2S:AlignmentXPosition',name='x') + y = Cpt(EpicsSignal, 'MD2S:AlignmentYPosition',name='y') + z = Cpt(EpicsSignal, 'MD2S:AlignmentZPosition',name='z') + cx = Cpt(EpicsSignal, 'MD2S:CenteringXPosition',name='cx') + cy = Cpt(EpicsSignal, 'MD2S:CenteringYPosition',name='cy') + class VerticalDCM(Device): b = Cpt(EpicsMotor, '-Ax:B}Mtr') g = Cpt(EpicsMotor, '-Ax:G}Mtr') @@ -199,6 +208,8 @@ class SampleXYZ(Device): govs = _make_governors("XF:19IDC-ES", name="govs") gov_robot = govs.gov.Robot + md2 = MD2Device(name="md2") + back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") From 05a7a28bbb83c00e0f7f498a731d995a8168a5d3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 13:00:22 -0400 Subject: [PATCH 055/234] Further Ophydizing --- start_bs.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 179 insertions(+), 8 deletions(-) diff --git a/start_bs.py b/start_bs.py index 75b3776d..cca2da4a 100755 --- a/start_bs.py +++ b/start_bs.py @@ -87,14 +87,179 @@ class ABBIXMercury(Mercury1, SoftDXPTrigger): pass +class MD2Positioner(PVPositioner): + setpoint = Cpt(EpicsSignal, 'Position', name='setpoint') + readback = Cpt(EpicsSignal, 'Position', name='readback') + state = Cpt(EpicsSignalRO, 'State', name='state') + done = Cpt(EpicsSignalRO, 'State', name='done') + precision = Cpt(EpicsSignalRO, 'Precision', name='precision') + done_value = 4 # MD2 Enum, 4 = Ready + # TODO: Add limits, settle_time, timeout or defaults for each + + def val(self): + return self.get().readback + +class FrontLightDevice(Device): + control = Cpt(EpicsSignal, 'FrontLightIsOn', name='control') + factor = Cpt(EpicsSignal, 'FrontLightFactor', name='factor') + level = Cpt(EpicsSignal, 'FrontLightLevel', name='level') + + def is_on(self): + return self.control.get() == 1 + + def turn_on(self): + self.control.set(1) + + def turn_off(self): + self.control.set(0) + + def set_factor(self, factor): + self.factor.set(factor) + + def set_level(self, level): + self.level.set(level) + +class BeamstopDevice(Device): + distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") + x = Cpt(MD2Positioner, "BeamstopX", name="x") + y = Cpt(MD2Positioner, "BeamstopY", name="y") + z = Cpt(MD2Positioner, "BeamstopZ", name="z") + position = Cpt(EpicsSignal, "BeamstopPosition", name="position") + +class MD2SimpleHVDevice(Device): + horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") + vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") + position = Cpt(EpicsSignal, "HVPosition", name="position") + # Current aperture/scintillator/capillary predifined position. + # Enum: the aperture position: + # 0: PARK, under cover. + # 1: BEAM, selected aperture aligned with beam. + # 2: OFF, just below the OAV. + # 3: UNKNOWN, not in a predefined position (this cannot be set). class MD2Device(Device): - omega = Cpt(EpicsSignal, 'MD2S:OmegaPosition',name='omega') - x = Cpt(EpicsSignal, 'MD2S:AlignmentXPosition',name='x') - y = Cpt(EpicsSignal, 'MD2S:AlignmentYPosition',name='y') - z = Cpt(EpicsSignal, 'MD2S:AlignmentZPosition',name='z') - cx = Cpt(EpicsSignal, 'MD2S:CenteringXPosition',name='cx') - cy = Cpt(EpicsSignal, 'MD2S:CenteringYPosition',name='cy') + omega = Cpt(MD2Positioner, 'Omega',name='omega') + x = Cpt(MD2Positioner, 'AlignmentX',name='x') + y = Cpt(MD2Positioner, 'AlignmentY',name='y') + z = Cpt(MD2Positioner, 'AlignmentZ',name='z') + cx = Cpt(MD2Positioner, 'CentringX',name='cx') + cy = Cpt(MD2Positioner, 'CentringY',name='cy') + phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') + detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') + detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') + + def standard_scan(self, + frame_number=0, # int: frame ID just for logging purposes. + num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. + start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=1, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=0.1, # double: exposure time (sec) to control shutter command. + num_passes=1 # int: number of moves forward and reverse between start angle and stop angle + ): + command = 'startScanEx2' + if start_angle is None: + start_angle=self.omega.get() + return self.exporter.cmd(command, [frame_number, num_images, start_angle, scan_range, exposure_time, num_passes]) + + def vector_scan(self, + start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=10, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=1, # double: exposure time (sec) to control shutter command. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + stop_y=None, # double: PhiY axis position at the end of the exposure. + stop_z=None, # double: PhiZ axis position at the end of the exposure. + stop_cx=None, # double: CentringX axis position at the end of the exposure. + stop_cy=None, # double: CentringY axis position at the end of the exposure. + ): + command = 'startScan4DEx' + if start_angle is None: + start_angle = self.omega.val() + if start_y is None: + start_y = self.y.val() + if start_z is None: + start_z = self.z.val() + if start_cx is None: + start_cx = self.cx.val() + if start_cy is None: + start_cy = self.cy.val() + if stop_y is None: + stop_y = self.y.val()+0.1 + if stop_z is None: + stop_z = self.z.val()+0.1 + if stop_cx is None: + stop_cx = self.cx.val()+0.1 + if stop_cy is None: + stop_cy = self.cy.val()+0.1 + + # List of scan parameters values, comma separated. The axes start values define the beginning + # of the exposure, that is when all the axes have a steady speed and when the shutter/detector + # are triggered. + # The axes stop values are for the end of detector exposure and define the position at the + # beginning of the deceleration. + # Inputs names: "start_angle", "scan_range", "exposure_time", "start_y", "start_z", "start_cx", + # "start_cy", "stop_y", "stop_z", "stop_cx", "stop_cy" + param_list = [start_angle, scan_range, exposure_time, + start_y, start_z, start_cx, start_cy, + stop_y, stop_z, stop_cx, stop_cy] + return self.exporter.cmd(command, param_list) + + def raster_scan(self, + omega_range=0, # double: omega relative move angle (deg) before closing the shutter. + line_range=0.1, # double: horizontal range of the grid (mm). + total_uturn_range=0.1, # double: vertical range of the grid (mm). + start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + number_of_lines=5, # int: number of frames on the vertical range. + frames_per_line=5, # int: number of frames on the horizontal range. + exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given + invert_direction=True, # boolean: true to enable passes in the reverse direction. + use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. + use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). + ): + + command = 'startRasterScanEx' + if start_omega is None: + start_omega = self.omega.val() + if start_y is None: + start_y = self.y.val() + if start_z is None: + start_z = self.z.val() + if start_cx is None: + start_cx = self.cx.val() + if start_cy is None: + start_cy = self.cy.val() + # List of scan parameters values, "/t" separated. The axes start values define the beginning + # of the exposure, that is when all the axes have a steady speed and when the shutter/detector + # are triggered. + # The axes stop values are for the end of detector exposure and define the position at the + # beginning of the deceleration. + # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", + # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", + # "invert_direction", "use_centring_table", "use_fast_mesh_scans" + param_list = [omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, + start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, + invert_direction, use_table_centering, use_fast_mesh_scans] + return self.exporter.cmd(command, param_list) + +class ShutterDevice(Device): + control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal + pos_opn = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Opn-I', name='pos_opn') + pos_cls = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Cls-I', name='pos_cls') + + def is_open(self): + return self.control.get() == 1 #self.pos_opn.get() + + def open_shutter(self): + self.control.set(1)#self.pos_opn.get()) iocs are down, so just setting it to 1 + + def close_shutter(self): + self.control.set(0)#self.pos_cls.get()) class VerticalDCM(Device): b = Cpt(EpicsMotor, '-Ax:B}Mtr') @@ -208,8 +373,14 @@ class SampleXYZ(Device): govs = _make_governors("XF:19IDC-ES", name="govs") gov_robot = govs.gov.Robot - md2 = MD2Device(name="md2") - + md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") + shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') + beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') + front_light = FrontLightDevice('XF:19IDC-ES{MD2}:', name='front_light') + aperature = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Aperature', name='aperature') + scintillator = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Scintillator', name='scintillator') + capillary = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Capillary', name='capillary') + back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") From 272293c25ef1b37125e0296c7fedc1f9f31ae2a8 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 13:28:50 -0400 Subject: [PATCH 056/234] Move device definitions to new file --- devices.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++ start_bs.py | 174 +-------------------------------------------------- 2 files changed, 176 insertions(+), 173 deletions(-) create mode 100644 devices.py diff --git a/devices.py b/devices.py new file mode 100644 index 00000000..d05b5818 --- /dev/null +++ b/devices.py @@ -0,0 +1,175 @@ +from ophyd import Device, Component as Cpt, EpicsSignal, EpicsSignalRO, PVPositioner + +class MD2Positioner(PVPositioner): + setpoint = Cpt(EpicsSignal, 'Position', name='setpoint') + readback = Cpt(EpicsSignal, 'Position', name='readback') + state = Cpt(EpicsSignalRO, 'State', name='state') + done = Cpt(EpicsSignalRO, 'State', name='done') + precision = Cpt(EpicsSignalRO, 'Precision', name='precision') + done_value = 4 # MD2 Enum, 4 = Ready + # TODO: Add limits, settle_time, timeout or defaults for each + + def val(self): + return self.get().readback + +class FrontLightDevice(Device): + control = Cpt(EpicsSignal, 'FrontLightIsOn', name='control') + factor = Cpt(EpicsSignal, 'FrontLightFactor', name='factor') + level = Cpt(EpicsSignal, 'FrontLightLevel', name='level') + + def is_on(self): + return self.control.get() == 1 + + def turn_on(self): + self.control.set(1) + + def turn_off(self): + self.control.set(0) + + def set_factor(self, factor): + self.factor.set(factor) + + def set_level(self, level): + self.level.set(level) + +class BeamstopDevice(Device): + distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") + x = Cpt(MD2Positioner, "BeamstopX", name="x") + y = Cpt(MD2Positioner, "BeamstopY", name="y") + z = Cpt(MD2Positioner, "BeamstopZ", name="z") + position = Cpt(EpicsSignal, "BeamstopPosition", name="position") + +class MD2SimpleHVDevice(Device): + horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") + vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") + position = Cpt(EpicsSignal, "HVPosition", name="position") + # Current aperture/scintillator/capillary predifined position. + # Enum: the aperture position: + # 0: PARK, under cover. + # 1: BEAM, selected aperture aligned with beam. + # 2: OFF, just below the OAV. + # 3: UNKNOWN, not in a predefined position (this cannot be set). + +class MD2Device(Device): + omega = Cpt(MD2Positioner, 'Omega',name='omega') + x = Cpt(MD2Positioner, 'AlignmentX',name='x') + y = Cpt(MD2Positioner, 'AlignmentY',name='y') + z = Cpt(MD2Positioner, 'AlignmentZ',name='z') + cx = Cpt(MD2Positioner, 'CentringX',name='cx') + cy = Cpt(MD2Positioner, 'CentringY',name='cy') + phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') + detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') + detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') + + def standard_scan(self, + frame_number=0, # int: frame ID just for logging purposes. + num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. + start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=1, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=0.1, # double: exposure time (sec) to control shutter command. + num_passes=1 # int: number of moves forward and reverse between start angle and stop angle + ): + command = 'startScanEx2' + if start_angle is None: + start_angle=self.omega.get() + return self.exporter.cmd(command, [frame_number, num_images, start_angle, scan_range, exposure_time, num_passes]) + + def vector_scan(self, + start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=10, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=1, # double: exposure time (sec) to control shutter command. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + stop_y=None, # double: PhiY axis position at the end of the exposure. + stop_z=None, # double: PhiZ axis position at the end of the exposure. + stop_cx=None, # double: CentringX axis position at the end of the exposure. + stop_cy=None, # double: CentringY axis position at the end of the exposure. + ): + command = 'startScan4DEx' + if start_angle is None: + start_angle = self.omega.val() + if start_y is None: + start_y = self.y.val() + if start_z is None: + start_z = self.z.val() + if start_cx is None: + start_cx = self.cx.val() + if start_cy is None: + start_cy = self.cy.val() + if stop_y is None: + stop_y = self.y.val()+0.1 + if stop_z is None: + stop_z = self.z.val()+0.1 + if stop_cx is None: + stop_cx = self.cx.val()+0.1 + if stop_cy is None: + stop_cy = self.cy.val()+0.1 + + # List of scan parameters values, comma separated. The axes start values define the beginning + # of the exposure, that is when all the axes have a steady speed and when the shutter/detector + # are triggered. + # The axes stop values are for the end of detector exposure and define the position at the + # beginning of the deceleration. + # Inputs names: "start_angle", "scan_range", "exposure_time", "start_y", "start_z", "start_cx", + # "start_cy", "stop_y", "stop_z", "stop_cx", "stop_cy" + param_list = [start_angle, scan_range, exposure_time, + start_y, start_z, start_cx, start_cy, + stop_y, stop_z, stop_cx, stop_cy] + return self.exporter.cmd(command, param_list) + + def raster_scan(self, + omega_range=0, # double: omega relative move angle (deg) before closing the shutter. + line_range=0.1, # double: horizontal range of the grid (mm). + total_uturn_range=0.1, # double: vertical range of the grid (mm). + start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + number_of_lines=5, # int: number of frames on the vertical range. + frames_per_line=5, # int: number of frames on the horizontal range. + exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given + invert_direction=True, # boolean: true to enable passes in the reverse direction. + use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. + use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). + ): + + command = 'startRasterScanEx' + if start_omega is None: + start_omega = self.omega.val() + if start_y is None: + start_y = self.y.val() + if start_z is None: + start_z = self.z.val() + if start_cx is None: + start_cx = self.cx.val() + if start_cy is None: + start_cy = self.cy.val() + # List of scan parameters values, "/t" separated. The axes start values define the beginning + # of the exposure, that is when all the axes have a steady speed and when the shutter/detector + # are triggered. + # The axes stop values are for the end of detector exposure and define the position at the + # beginning of the deceleration. + # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", + # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", + # "invert_direction", "use_centring_table", "use_fast_mesh_scans" + param_list = [omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, + start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, + invert_direction, use_table_centering, use_fast_mesh_scans] + return self.exporter.cmd(command, param_list) + +class ShutterDevice(Device): + control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal + pos_opn = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Opn-I', name='pos_opn') + pos_cls = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Cls-I', name='pos_cls') + + def is_open(self): + return self.control.get() == 1 #self.pos_opn.get() + + def open_shutter(self): + self.control.set(1)#self.pos_opn.get()) iocs are down, so just setting it to 1 + + def close_shutter(self): + self.control.set(0)#self.pos_cls.get()) \ No newline at end of file diff --git a/start_bs.py b/start_bs.py index cca2da4a..ad83bb79 100755 --- a/start_bs.py +++ b/start_bs.py @@ -8,6 +8,7 @@ import os from mxtools.governor import _make_governors from ophyd.signal import EpicsSignalBase +from devices import FrontLightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, TopAlignerFast, TopAlignerSlow, GoniometerStack) @@ -87,179 +88,6 @@ class ABBIXMercury(Mercury1, SoftDXPTrigger): pass -class MD2Positioner(PVPositioner): - setpoint = Cpt(EpicsSignal, 'Position', name='setpoint') - readback = Cpt(EpicsSignal, 'Position', name='readback') - state = Cpt(EpicsSignalRO, 'State', name='state') - done = Cpt(EpicsSignalRO, 'State', name='done') - precision = Cpt(EpicsSignalRO, 'Precision', name='precision') - done_value = 4 # MD2 Enum, 4 = Ready - # TODO: Add limits, settle_time, timeout or defaults for each - - def val(self): - return self.get().readback - -class FrontLightDevice(Device): - control = Cpt(EpicsSignal, 'FrontLightIsOn', name='control') - factor = Cpt(EpicsSignal, 'FrontLightFactor', name='factor') - level = Cpt(EpicsSignal, 'FrontLightLevel', name='level') - - def is_on(self): - return self.control.get() == 1 - - def turn_on(self): - self.control.set(1) - - def turn_off(self): - self.control.set(0) - - def set_factor(self, factor): - self.factor.set(factor) - - def set_level(self, level): - self.level.set(level) - -class BeamstopDevice(Device): - distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") - x = Cpt(MD2Positioner, "BeamstopX", name="x") - y = Cpt(MD2Positioner, "BeamstopY", name="y") - z = Cpt(MD2Positioner, "BeamstopZ", name="z") - position = Cpt(EpicsSignal, "BeamstopPosition", name="position") - -class MD2SimpleHVDevice(Device): - horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") - vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") - position = Cpt(EpicsSignal, "HVPosition", name="position") - # Current aperture/scintillator/capillary predifined position. - # Enum: the aperture position: - # 0: PARK, under cover. - # 1: BEAM, selected aperture aligned with beam. - # 2: OFF, just below the OAV. - # 3: UNKNOWN, not in a predefined position (this cannot be set). - -class MD2Device(Device): - omega = Cpt(MD2Positioner, 'Omega',name='omega') - x = Cpt(MD2Positioner, 'AlignmentX',name='x') - y = Cpt(MD2Positioner, 'AlignmentY',name='y') - z = Cpt(MD2Positioner, 'AlignmentZ',name='z') - cx = Cpt(MD2Positioner, 'CentringX',name='cx') - cy = Cpt(MD2Positioner, 'CentringY',name='cy') - phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') - detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') - detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') - - def standard_scan(self, - frame_number=0, # int: frame ID just for logging purposes. - num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. - start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=1, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=0.1, # double: exposure time (sec) to control shutter command. - num_passes=1 # int: number of moves forward and reverse between start angle and stop angle - ): - command = 'startScanEx2' - if start_angle is None: - start_angle=self.omega.get() - return self.exporter.cmd(command, [frame_number, num_images, start_angle, scan_range, exposure_time, num_passes]) - - def vector_scan(self, - start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=10, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=1, # double: exposure time (sec) to control shutter command. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - stop_y=None, # double: PhiY axis position at the end of the exposure. - stop_z=None, # double: PhiZ axis position at the end of the exposure. - stop_cx=None, # double: CentringX axis position at the end of the exposure. - stop_cy=None, # double: CentringY axis position at the end of the exposure. - ): - command = 'startScan4DEx' - if start_angle is None: - start_angle = self.omega.val() - if start_y is None: - start_y = self.y.val() - if start_z is None: - start_z = self.z.val() - if start_cx is None: - start_cx = self.cx.val() - if start_cy is None: - start_cy = self.cy.val() - if stop_y is None: - stop_y = self.y.val()+0.1 - if stop_z is None: - stop_z = self.z.val()+0.1 - if stop_cx is None: - stop_cx = self.cx.val()+0.1 - if stop_cy is None: - stop_cy = self.cy.val()+0.1 - - # List of scan parameters values, comma separated. The axes start values define the beginning - # of the exposure, that is when all the axes have a steady speed and when the shutter/detector - # are triggered. - # The axes stop values are for the end of detector exposure and define the position at the - # beginning of the deceleration. - # Inputs names: "start_angle", "scan_range", "exposure_time", "start_y", "start_z", "start_cx", - # "start_cy", "stop_y", "stop_z", "stop_cx", "stop_cy" - param_list = [start_angle, scan_range, exposure_time, - start_y, start_z, start_cx, start_cy, - stop_y, stop_z, stop_cx, stop_cy] - return self.exporter.cmd(command, param_list) - - def raster_scan(self, - omega_range=0, # double: omega relative move angle (deg) before closing the shutter. - line_range=0.1, # double: horizontal range of the grid (mm). - total_uturn_range=0.1, # double: vertical range of the grid (mm). - start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - number_of_lines=5, # int: number of frames on the vertical range. - frames_per_line=5, # int: number of frames on the horizontal range. - exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given - invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. - use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). - ): - - command = 'startRasterScanEx' - if start_omega is None: - start_omega = self.omega.val() - if start_y is None: - start_y = self.y.val() - if start_z is None: - start_z = self.z.val() - if start_cx is None: - start_cx = self.cx.val() - if start_cy is None: - start_cy = self.cy.val() - # List of scan parameters values, "/t" separated. The axes start values define the beginning - # of the exposure, that is when all the axes have a steady speed and when the shutter/detector - # are triggered. - # The axes stop values are for the end of detector exposure and define the position at the - # beginning of the deceleration. - # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", - # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", - # "invert_direction", "use_centring_table", "use_fast_mesh_scans" - param_list = [omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, - start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, - invert_direction, use_table_centering, use_fast_mesh_scans] - return self.exporter.cmd(command, param_list) - -class ShutterDevice(Device): - control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal - pos_opn = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Opn-I', name='pos_opn') - pos_cls = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Cls-I', name='pos_cls') - - def is_open(self): - return self.control.get() == 1 #self.pos_opn.get() - - def open_shutter(self): - self.control.set(1)#self.pos_opn.get()) iocs are down, so just setting it to 1 - - def close_shutter(self): - self.control.set(0)#self.pos_cls.get()) class VerticalDCM(Device): b = Cpt(EpicsMotor, '-Ax:B}Mtr') From 2a6c9361130e6d60f4221d1445f65e7cd95701b1 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 13:47:46 -0400 Subject: [PATCH 057/234] Add extendable GonioDevice interface * for AMX/FMX and GUI use --- devices.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devices.py b/devices.py index d05b5818..3eac32b4 100644 --- a/devices.py +++ b/devices.py @@ -50,11 +50,13 @@ class MD2SimpleHVDevice(Device): # 2: OFF, just below the OAV. # 3: UNKNOWN, not in a predefined position (this cannot be set). -class MD2Device(Device): +class GonioDevice(Device): omega = Cpt(MD2Positioner, 'Omega',name='omega') x = Cpt(MD2Positioner, 'AlignmentX',name='x') y = Cpt(MD2Positioner, 'AlignmentY',name='y') z = Cpt(MD2Positioner, 'AlignmentZ',name='z') + +class MD2Device(GonioDevice): cx = Cpt(MD2Positioner, 'CentringX',name='cx') cy = Cpt(MD2Positioner, 'CentringY',name='cy') phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') From b78c028e123323aada37e1b567ce42c270ef69df Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 15:09:29 -0400 Subject: [PATCH 058/234] ophydized gui gonio calls --- gui/control_main.py | 101 +++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index cb2cfd68..04bba315 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -19,7 +19,8 @@ from qtpy import QtCore, QtGui, QtWidgets from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator -from qtpy.QtWidgets import QApplication, QCheckBox, QFrame, QGraphicsPixmapItem +from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication +from devices import GonioDevice import daq_utils import db_lib @@ -171,6 +172,7 @@ def __init__(self): self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], gov_message_pv_name=daq_utils.pvLookupDict["governorMessage"],) self.initUI() + self.initOphyd() self.govStateMessagePV = PV(daq_utils.pvLookupDict["governorMessage"]) self.zoom1FrameRatePV = PV(daq_utils.pvLookupDict["zoom1FrameRate"]) self.zoom2FrameRatePV = PV(daq_utils.pvLookupDict["zoom2FrameRate"]) @@ -214,17 +216,17 @@ def __init__(self): self.initCallbacks() if self.scannerType != "PI": self.motPos = { - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omega_pv.get(), + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), + "omega": self.gon.omega.val(), } else: self.motPos = { - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omega_pv.get(), + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), + "omega": self.gon.omega.val(), "fineX": self.sampFineX_pv.get(), "fineY": self.sampFineY_pv.get(), "fineZ": self.sampFineZ_pv.get(), @@ -1177,14 +1179,14 @@ def createSampleTab(self): setDC2CPButton.setFixedWidth(50) setDC2CPButton.clicked.connect(self.setDCStartCB) omegaLabel = QtWidgets.QLabel("Omega:") - omegaMonitorPV = str(getBlConfig("omegaMonitorPV")) + #omegaMonitorPV = str(getBlConfig("omegaMonitorPV")) self.sampleOmegaRBVLedit = QtEpicsPVLabel( - daq_utils.motor_dict["omega"] + "." + omegaMonitorPV, self, 70 + self.gon.omega.readback.pvname, self, 70 ) omegaSPLabel = QtWidgets.QLabel("SetPoint:") omegaSPLabel.setFixedWidth(70) self.sampleOmegaMoveLedit = QtEpicsPVEntry( - daq_utils.motor_dict["omega"] + ".VAL", self, 70, 2 + self.gon.omega.setpoint.pvname, self, 70, 2 ) self.sampleOmegaMoveLedit.getEntry().returnPressed.connect(self.moveOmegaCB) moveOmegaButton = QtWidgets.QPushButton("Move") @@ -1666,9 +1668,9 @@ def adjustGraphics4ZoomChange(self, fov): self.processSampMove(self.sampy_pv.get(), "y") self.processSampMove(self.sampz_pv.get(), "z") if self.centeringMarksList != []: - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") + self.processSampMove(self.gon.x.val(), "x") + self.processSampMove(self.gon.y.val(), "y") + self.processSampMove(self.gon.z.val(), "z") def flushBuffer(self, vidStream): if vidStream == None: @@ -2900,13 +2902,13 @@ def focusTweakCB(self, tv): if self.controlEnabled(): tvY = tvf * ( - math.cos(math.radians(view_omega_offset + self.motPos["omega"])) + math.cos(math.radians(view_omega_offset + self.gon.omega.val())) ) # these are opposite C2C tvZ = tvf * ( - math.sin(math.radians(view_omega_offset + self.motPos["omega"])) + math.sin(math.radians(view_omega_offset + self.gon.omega.val())) ) - self.sampyTweak_pv.put(tvY) - self.sampzTweak_pv.put(tvZ) + self.gon.y.move(self.gon.y.val() + tvY) + self.gon.z.move(self.gon.z.val() + tvZ) else: self.popupServerMessage("You don't have control") @@ -3267,9 +3269,9 @@ def saveCenterCB(self): marker.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) self.centeringMark = { "sampCoords": { - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), }, "graphicsItem": marker, "centerCursorX": self.centerMarker.x(), @@ -3398,10 +3400,10 @@ def definePolyRaster( "beamWidth": beamWidth, "beamHeight": beamHeight, "status": RasterStatus.NEW.value, - "x": self.sampx_pv.get() + self.sampFineX_pv.get(), - "y": self.sampy_pv.get() + self.sampFineY_pv.get(), - "z": self.sampz_pv.get() + self.sampFineZ_pv.get(), - "omega": self.omega_pv.get(), + "x": self.gon.x.val() + self.sampFineX_pv.get(), + "y": self.gon.y.val() + self.sampFineY_pv.get(), + "z": self.gon.z.val() + self.sampFineZ_pv.get(), + "omega": self.gon.omega.val(), "stepsize": stepsize, "rowDefs": [], } # just storing step as microns, not using her @@ -3411,10 +3413,10 @@ def definePolyRaster( "beamWidth": beamWidth, "beamHeight": beamHeight, "status": RasterStatus.NEW.value, - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omega_pv.get(), + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), + "omega": self.gon.omega.val(), "stepsize": stepsize, "rowDefs": [], } # just storing step as microns, not using here @@ -4107,9 +4109,9 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): centeringOption == "Interactive" and self.mountedPin_pv.get() == self.selectedSampleID ) or centeringOption == "Testing": # user centered manually - reqObj["pos_x"] = float(self.sampx_pv.get()) - reqObj["pos_y"] = float(self.sampy_pv.get()) - reqObj["pos_z"] = float(self.sampz_pv.get()) + reqObj["pos_x"] = float(self.gon.x.val()) + reqObj["pos_y"] = float(self.gon.y.val()) + reqObj["pos_z"] = float(self.gon.z.val()) reqObj["runNum"] = runNum try: reqObj["sweep_start"] = float(self.osc_start_ledit.text()) @@ -4538,13 +4540,13 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): and self.selectedSampleRequest["sample"] # with control enabled == self.mountedPin_pv.get() ): # And the sample of the selected request is mounted - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") + self.processSampMove(self.gon.x.val(), "x") + self.processSampMove(self.gon.y.val(), "y") + self.processSampMove(self.gon.z.val(), "z") if ( abs( selectedSampleRequest["request_obj"]["rasterDef"]["omega"] - - self.omega_pv.get() + - self.gon.omega.val() ) > 5.0 ): @@ -4832,6 +4834,9 @@ def pauseButtonStateCB(self, value=None, char_value=None, **kw): pauseButtonStateVar = value self.pauseButtonStateSignal.emit(pauseButtonStateVar) + def initOphyd(self): + self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") + def initUI(self): self.tabs = QtWidgets.QTabWidget() self.comm_pv = PV(daq_utils.beamlineComm + "command_s") @@ -5060,12 +5065,12 @@ def initCallbacks(self): self.energyChangeSignal.connect(self.processEnergyChange) self.energy_pv.add_callback(self.processEnergyChangeCB, motID="x") - self.sampx_pv = PV(daq_utils.motor_dict["sampleX"] + ".RBV") + self.sampx_pv = PV(self.gon.x.readback.pvname) self.sampMoveSignal.connect(self.processSampMove) self.sampx_pv.add_callback(self.processSampMoveCB, motID="x") - self.sampy_pv = PV(daq_utils.motor_dict["sampleY"] + ".RBV") + self.sampy_pv = PV(self.gon.y.readback.pvname) self.sampy_pv.add_callback(self.processSampMoveCB, motID="y") - self.sampz_pv = PV(daq_utils.motor_dict["sampleZ"] + ".RBV") + self.sampz_pv = PV(self.gon.z.readback.pvname) self.sampz_pv.add_callback(self.processSampMoveCB, motID="z") if self.scannerType == "PI": @@ -5076,14 +5081,14 @@ def initCallbacks(self): self.sampFineZ_pv = PV(daq_utils.motor_dict["fineZ"] + ".RBV") self.sampFineZ_pv.add_callback(self.processSampMoveCB, motID="fineZ") - self.omega_pv = PV(daq_utils.motor_dict["omega"] + ".VAL") - self.omegaTweak_pv = PV(daq_utils.motor_dict["omega"] + ".RLV") - self.sampyTweak_pv = PV(daq_utils.motor_dict["sampleY"] + ".RLV") - if daq_utils.beamline == "nyx": - self.sampzTweak_pv = PV(daq_utils.motor_dict["sampleX"] + ".RLV") - else: - self.sampzTweak_pv = PV(daq_utils.motor_dict["sampleZ"] + ".RLV") - self.omegaRBV_pv = PV(daq_utils.motor_dict["omega"] + ".RBV") + self.omega_pv = PV(self.gon.omega.setpoint.pvname) + self.omegaTweak_pv = PV(self.gon.omega.setpoint.pvname) + self.sampyTweak_pv = PV(self.gon.y.setpoint.pvname) + #if daq_utils.beamline == "nyx": + # self.sampzTweak_pv = PV(self.gon.x.setpoint.pvname + ".RLV") + #else: + self.sampzTweak_pv = PV(self.gon.z.setpoint.pvname) + self.omegaRBV_pv = PV(self.gon.omega.readback.pvname) self.omegaRBV_pv.add_callback( self.processSampMoveCB, motID="omega" ) # I think monitoring this allows for the textfield to monitor val and this to deal with the graphics. Else next line has two callbacks on same thing. From 9a61a7956a46c1d831e06b36af07c5a4070f4045 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 15:20:36 -0400 Subject: [PATCH 059/234] hijacking camera --- gui/control_main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index 04bba315..d2037597 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1049,6 +1049,9 @@ def createSampleTab(self): if self.zoom1FrameRatePV.get() != 0: self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') + + _thread.start_new_thread(self.initVideo3, (0.25,)) + #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag self.timerSample = QTimer() self.timerSample.timeout.connect(self.timerSampleRefresh) From 3647856ae7373e2139253b6678d53cee328cea02 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 15:24:54 -0400 Subject: [PATCH 060/234] refactor illegal position name --- devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices.py b/devices.py index 3eac32b4..21476f32 100644 --- a/devices.py +++ b/devices.py @@ -37,12 +37,12 @@ class BeamstopDevice(Device): x = Cpt(MD2Positioner, "BeamstopX", name="x") y = Cpt(MD2Positioner, "BeamstopY", name="y") z = Cpt(MD2Positioner, "BeamstopZ", name="z") - position = Cpt(EpicsSignal, "BeamstopPosition", name="position") + pos = Cpt(EpicsSignal, "BeamstopPosition", name="pos") class MD2SimpleHVDevice(Device): horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") - position = Cpt(EpicsSignal, "HVPosition", name="position") + pos = Cpt(EpicsSignal, "HVPosition", name="pos") # Current aperture/scintillator/capillary predifined position. # Enum: the aperture position: # 0: PARK, under cover. From 87be6a4a15f9959e4e952ba1749656da763c6834 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 28 Aug 2023 16:54:48 -0400 Subject: [PATCH 061/234] MD2 C2C --- daq_lib.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/daq_lib.py b/daq_lib.py index 6221b8a0..30113e4f 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -768,6 +768,20 @@ def checkC2C_X(x,fovx): # this is to make sure the user doesn't make too much of def center_on_click(x,y,fovx,fovy,source="screen",maglevel=0,jog=0,viewangle=daq_utils.CAMERA_ANGLE_BEAM): #maglevel=0 means lowmag, high fov, #1 = himag with digizoom option, #source=screen = from screen click, otherwise from macro with full pixel dimensions #viewangle=daq_utils.CAMERA_ANGLE_BEAM, default camera angle is in-line with the beam + + if daq_utils.beamline == "nyx": + lsdc_x = daq_utils.screenPixX + lsdc_y = daq_utils.screenPixY + md2_x = getPvDesc("md2CenterPixelX") * 2 + md2_y = getPvDesc("md2CenterPixelY") * 2 + scale_x = md2_x / lsdc_x + scale_y = md2_y / lsdc_y + x = x * scale_x + y = y * scale_y + str_coords = f'x: {x}, y: {y}' + setPvDesc("MD2C2C", str_coords) + return + if (getBlConfig('robot_online')): #so that we don't move things when robot moving? robotGovState = (getPvDesc("robotSaActive") or getPvDesc("humanSaActive")) if (not robotGovState): From 0b3a48899cd3843fd2416140773e94250a2d9ed6 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 15:14:19 -0400 Subject: [PATCH 062/234] Add Exporter Component --- devices.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/devices.py b/devices.py index 21476f32..cb5c95e3 100644 --- a/devices.py +++ b/devices.py @@ -1,4 +1,7 @@ from ophyd import Device, Component as Cpt, EpicsSignal, EpicsSignalRO, PVPositioner +import socket +from ophyd.status import SubscriptionStatus +import os class MD2Positioner(PVPositioner): setpoint = Cpt(EpicsSignal, 'Position', name='setpoint') @@ -12,6 +15,86 @@ class MD2Positioner(PVPositioner): def val(self): return self.get().readback +class ExporterComponent(Cpt): + def __init__(self, address, port, name, **kwargs): + super().__init__(self, **kwargs) + self.name = name + self.address = address + self.port = port + self.sock = None # socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #self.sock.settimeout(5) + + def get(self): + return () + + def connect(self): + # for establishing connection, using context manager is preferred + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.address, self.port)) + + def disconnect(self): + self.sock.close() + self.sock = None + + def send_data(self, data): + STX = chr(2) + ETX = chr(3) + data = STX + data + ETX + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((self.address, self.port)) + s.sendto(data.encode(), (self.address, self.port)) + state = s.recv(4096) + print(f'state: {self.decipher_reply(state)}') + ret = None + while ret == None: + output = s.recv(4096) + output = self.decipher_reply(output) + ret = self.process_ret(output) + return ret + + def read_stream(self): + output = None + while output == None: + output, addr = self.sock.recvfrom(4096) + print(f'output:{self.decipher_reply(output)}') + output = None + + def write(self, attribute, value): + return self.send_data('WRTE ' + attribute + ' ' + str(value)) + + def read(self, attribute): + return (self.send_data('READ ' + attribute + ' ')).split(" ")[0][4:] + + def cmd(self, method, parameters): + parameters = map(str, parameters) + params = "\t".join(parameters) + return self.send_data('EXEC ' + method + ' ' + str(params)) + + def decipher_reply(self, reply): + # Specifically for decoding MD2 Exporter replies + reply = str(reply) + reply = reply.replace("\\x03", "") + reply = reply.replace("\\x1f", ", ") + reply = reply.replace("\\x02", "\n") + reply = reply.replace("\\t", " ") + return reply[2:-1] + + def process_ret(self, ret): + # Returns only when an error or return value is found, + # ignores events to ensure flyer kickoff functions as expected + for line in ret.split("\n"): + if "ERR:" in line: + print(f"error: {line}") + return line + elif "RET:" in line: + print(f"returned: {line}") + return line + elif "NULL" in line: + print(f"null error: {line}") + return line + elif "EVT:" in line: + pass #return self.process_evt(line) + class FrontLightDevice(Device): control = Cpt(EpicsSignal, 'FrontLightIsOn', name='control') factor = Cpt(EpicsSignal, 'FrontLightFactor', name='factor') @@ -59,15 +142,25 @@ class GonioDevice(Device): class MD2Device(GonioDevice): cx = Cpt(MD2Positioner, 'CentringX',name='cx') cy = Cpt(MD2Positioner, 'CentringY',name='cy') + state = Cpt(EpicsSignalRO, 'State',name='state') + phase = Cpt(EpicsSignal, 'CurrentPhase',name='phase') phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') + exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=os.environ['EXPORTER_PORT'], name='exporter') + + def ready_status(self): + # returns an ophyd status object that monitors the state pv for operations to complete + def check_ready(*, old_value, value, **kwargs): + "Return True when the MD2 is ready" + return (old_value == 3 and value == 2) + return SubscriptionStatus(self.state, check_ready) def standard_scan(self, frame_number=0, # int: frame ID just for logging purposes. num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=1, # double: omega relative move angle (deg) before closing the shutter. + scan_range=0.1, # double: omega relative move angle (deg) before closing the shutter. exposure_time=0.1, # double: exposure time (sec) to control shutter command. num_passes=1 # int: number of moves forward and reverse between start angle and stop angle ): From ad203d7dbc43879c5ea6bcd09ce61c67b6e2d22e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 15:17:51 -0400 Subject: [PATCH 063/234] Testing new MD2 flyers --- md2_flyers.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 md2_flyers.py diff --git a/md2_flyers.py b/md2_flyers.py new file mode 100644 index 00000000..668a44af --- /dev/null +++ b/md2_flyers.py @@ -0,0 +1,111 @@ +import logging +import os +from collections import deque +import getpass +import grp +from ophyd.sim import NullStatus +from ophyd.status import SubscriptionStatus + +logger = logging.getLogger(__name__) + +DEFAULT_DATUM_DICT = {"data": None, "omega": None} + +INTERNAL_SERIES = 0 +INTERNAL_ENABLE = 1 +EXTERNAL_SERIES = 2 +EXTERNAL_ENABLE = 3 + +class MD2StandardFlyer(): + # this is a bluesky flyer class + def __init__(self, md2, detector=None) -> None: + self.name = "MD2Flyer" + self.detector = detector + self.md2 = md2 + self.collection_params = {} + + self._asset_docs_cache = deque() + self._resource_uids = [] + self._datum_counter = None + self._datum_ids = DEFAULT_DATUM_DICT + self._master_file = None + self._master_metadata = [] + + self._collection_dictionary = None + + def kickoff(self): + self.md2.standard_scan(num_images=self.collection_params["total_num_images"], + start_angle=self.collection_params["start_angle"], + scan_range=self.collection_params["img_width"], + exposure_time=self.collection_params["exposure_per_image"]) + + return NullStatus() + + def update_parameters(self, angle_start, img_width, total_num_images, exposure_per_image): + self.collection_params = { + "angle_start": angle_start, + "img_width": img_width, + "total_num_images": total_num_images, + "exposure_per_image": exposure_per_image, + } + + def configure_detector(self, *args, **kwargs): + file_prefix = kwargs["file_prefix"] + data_directory_name = kwargs["data_directory_name"] + self.detector.file.external_name.put(file_prefix, wait=True) + self.detector.file.write_path_template = data_directory_name + + def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_image, + file_prefix, data_directory_name, file_number_start, x_beam, y_beam, + wavelength, det_distance_m, num_images_per_file): + self.detector.cam.save_files.put(1) + self.detector.cam.file_owner.put(getpass.getuser()) + self.detector.cam.file_owner_grp.put(grp.getgrgid(os.getgid())[0]) + self.detector.cam.file_perms.put(420) + file_prefix_minus_directory = str(file_prefix) + file_prefix_minus_directory = file_prefix_minus_directory.split("/")[-1] + self.detector.cam.acquire_time.put(exposure_per_image) + self.detector.cam.acquire_period.put(exposure_per_image) + self.detector.cam.num_triggers.put(total_num_images) + self.detector.cam.file_path.put(data_directory_name, wait=True) + self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id", wait=True) + self.detector.cam.sequence_id.put(file_number_start, wait=True) + self.detector.cam.beam_center_x.put(x_beam) + self.detector.cam.beam_center_y.put(y_beam) + self.detector.cam.omega_incr.put(img_width) + self.detector.cam.omega_start.put(angle_start) + self.detector.cam.wavelength.put(wavelength) + self.detector.cam.det_distance.put(det_distance_m) + self.detector.cam.trigger_mode.put( + EXTERNAL_ENABLE + ) # must be external_enable to get the correct number of triggers and stop acquire + self.detector.file.file_write_images_per_file.put(num_images_per_file, wait=True) + + def armed_callback(value, old_value, **kwargs): + if old_value == 0 and value == 1: + return True + return False + + status = SubscriptionStatus(self.detector.cam.armed, armed_callback, run=False) + self.detector.cam.acquire.put(1) + yield status + + def complete(self): + # monitor md2 status, wait for ready and then return + yield NullStatus() + + def describe_collect(self): + return {"stream_name": {}} + #return {self.name: self._collection_dictionary} + + def collect(self): + logger.debug("raster_flyer.collect(): going to unstage now") + yield {"data": {}, "timestamps": {}, "time": 0, "seq_num": 0} + #return self._collection_dictionary + + def unstage(self): + logger.debug("flyer unstaging") + self.collection_params = {} + + def collect_asset_docs(self): + for _ in (): + yield _ \ No newline at end of file From d2e9a26a6a4e4713089409aedf5c4c03ba2a166e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 15:24:18 -0400 Subject: [PATCH 064/234] filled out complete function --- md2_flyers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 668a44af..445b04ce 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -16,7 +16,6 @@ EXTERNAL_ENABLE = 3 class MD2StandardFlyer(): - # this is a bluesky flyer class def __init__(self, md2, detector=None) -> None: self.name = "MD2Flyer" self.detector = detector @@ -37,7 +36,6 @@ def kickoff(self): start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["img_width"], exposure_time=self.collection_params["exposure_per_image"]) - return NullStatus() def update_parameters(self, angle_start, img_width, total_num_images, exposure_per_image): @@ -90,8 +88,10 @@ def armed_callback(value, old_value, **kwargs): yield status def complete(self): - # monitor md2 status, wait for ready and then return - yield NullStatus() + # monitor md2 status, wait for ready or timeout and then return + ready_status = self.md2.ready_status() + ready_status.wait(timeout=20) + return ready_status def describe_collect(self): return {"stream_name": {}} From a2172c273e454058133c8b4a77f807e5338bb4fa Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 16:09:46 -0400 Subject: [PATCH 065/234] md2 standard collection and flyer --- daq_lib.py | 38 ++++++++++++++---------- daq_macros.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++- devices.py | 1 + md2_flyers.py | 16 +++++------ 4 files changed, 110 insertions(+), 25 deletions(-) diff --git a/daq_lib.py b/daq_lib.py index 30113e4f..bca75c1e 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -701,22 +701,30 @@ def collect_detector_seq_hw(sweep_start,range_degrees,image_width,exposure_perio file_prefix_minus_directory = file_prefix_minus_directory[file_prefix_minus_directory.rindex("/")+1:len(file_prefix_minus_directory)] except ValueError: pass + logger.info("collect %f degrees for %f seconds %d images exposure_period = %f exposure_time = %f" % (range_degrees,range_seconds,number_of_images,exposure_period,exposure_time)) - if (protocol == "standard" or protocol == "characterize" or protocol == "ednaCol" or protocol == "burn"): - logger.info("vectorSync " + str(time.time())) - daq_macros.vectorSync() - logger.info("zebraDaq " + str(time.time())) - - vector_params = daq_macros.gatherStandardVectorParams() - logger.debug(f"vector_params: {vector_params}") - RE(daq_macros.standard_plan(flyer,angleStart,number_of_images,range_degrees,image_width,exposure_period,file_prefix_minus_directory,data_directory_name,file_number, vector_params, file_prefix_minus_directory)) - - elif (protocol == "vector"): - RE(daq_macros.vectorZebraScan(currentRequest)) - elif (protocol == "stepVector"): - daq_macros.vectorZebraStepScan(currentRequest) - else: - pass + + if(getBlConfig("ophydCollections") == True): + logger.info("ophyd collections enabled") + if (protocol == "standard"): + RE(daq_macros.standard_plan_wrapped(currentRequest)) + elif (protocol == "vector"): + RE(daq_macros.vector_plan_wrapped(currentRequest)) + else: + if (protocol == "standard" or protocol == "characterize" or protocol == "ednaCol" or protocol == "burn"): + logger.info("vectorSync " + str(time.time())) + daq_macros.vectorSync() + logger.info("zebraDaq " + str(time.time())) + + vector_params = daq_macros.gatherStandardVectorParams() + logger.debug(f"vector_params: {vector_params}") + RE(daq_macros.standard_zebra_plan(flyer,angleStart,number_of_images,range_degrees,image_width,exposure_period,file_prefix_minus_directory,data_directory_name,file_number, vector_params, file_prefix_minus_directory)) + elif (protocol == "vector"): + RE(daq_macros.vectorZebraScan(currentRequest)) + elif (protocol == "stepVector"): + daq_macros.vectorZebraStepScan(currentRequest) + else: + pass return diff --git a/daq_macros.py b/daq_macros.py index cb777541..21c85381 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3647,10 +3647,88 @@ def gatherStandardVectorParams(): vectorParams["transmission"]=transmission return vectorParams -def standard_plan(flyer,angle_start,num_images,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start, vector_params, data_path): +def standard_zebra_plan(flyer,angle_start,num_images,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start, vector_params, data_path): final_plan = finalize_wrapper(zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path), bps.mv(flyer.detector.cam.acquire, 0)) yield from final_plan +def standard_plan_wrapped(currentRequest): + yield from finalize_wrapper(standardDaq(currentRequest), clean_up_collection(currentRequest)) + +def vector_plan_wrapped(currentRequest): + yield from finalize_wrapper(vectorDaq(currentRequest), clean_up_collection(currentRequest)) + +def standardDaq(currentRequest): + # collect all parameters + # perform preparatory movements + # arm the detector + # perform governor and phase transitions + # update flyer parameters + # flylogger.info("gathering vector parameters") + x_beam = getPvDesc("beamCenterX") + y_beam = getPvDesc("beamCenterY") + wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 + reqObj = currentRequest["request_obj"] + file_prefix = str(reqObj["file_prefix"]) + data_directory_name = str(reqObj["directory"]) + file_number_start = reqObj["file_number_start"] + sweep_start_angle = reqObj["sweep_start"] + sweep_end_angle = reqObj["sweep_end"] + exposure_time = reqObj["exposure_time"] + file_prefix = str(reqObj["file_prefix"]) + data_directory_name = str(reqObj["directory"]) + file_number_start = reqObj["file_number_start"] + img_width = reqObj["img_width"] + exposure_per_image = reqObj["exposure_time"] + total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + scan_range = float(total_num_images)*img_width + angle_start = sweep_start_angle + num_images_per_file = total_num_images + wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + + if flyer.detector.cam.armed.get() == 1: + daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' + 'should check the most recent collection to determine if it was successful. Cancelling' + 'this collection, retry when ready.') + logger.warning("Detector was in the armed state prior to this attempted collection.") + return 0 + start_time = time.time() + flyer.configure_detector(file_prefix, data_directory_name) + flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, + file_prefix, data_directory_name, file_number_start, x_beam, y_beam, + wavelength, det_distance_m, num_images_per_file) + def armed_callback(value, old_value, **kwargs): + return (old_value == 0 and value == 1) + arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) + flyer.detector.cam.acquire.put(1) + govStatus = gov_lib.setGovRobot(gov_robot, "DA") + arm_status.wait(timeout=20) + govStatus.wait(timeout=20) + logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") + if govStatus.exception(): + logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") + return + md2.phase.set(2) # TODO: Enum for MD2 phases and states + md2.ready_status().wait(timeout=20) + flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) + yield from bp.fly([flyer]) + +def vectorDaq(currentRequest): + # collect all parameters + # perform preparatory movements + # arm the detector + # perform governor transitions + # update flyer parameters + # fly + pass + +def clean_up_collection(): + # this is a plan that should will always be run after a collection is complete + yield from bps.mv(flyer.detector.cam.acquire, 0) + if (lastOnSample()): + gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) + gov_status.wait() + def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): logger.info("in Zebra Daq Bluesky #1") diff --git a/devices.py b/devices.py index cb5c95e3..0efd1bd1 100644 --- a/devices.py +++ b/devices.py @@ -140,6 +140,7 @@ class GonioDevice(Device): z = Cpt(MD2Positioner, 'AlignmentZ',name='z') class MD2Device(GonioDevice): + # TODO: Enum for MD2 phases and states cx = Cpt(MD2Positioner, 'CentringX',name='cx') cy = Cpt(MD2Positioner, 'CentringY',name='cy') state = Cpt(EpicsSignalRO, 'State',name='state') diff --git a/md2_flyers.py b/md2_flyers.py index 445b04ce..c4791b3b 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -34,21 +34,19 @@ def __init__(self, md2, detector=None) -> None: def kickoff(self): self.md2.standard_scan(num_images=self.collection_params["total_num_images"], start_angle=self.collection_params["start_angle"], - scan_range=self.collection_params["img_width"], - exposure_time=self.collection_params["exposure_per_image"]) + scan_range=self.collection_params["scan_range"], + exposure_time=self.collection_params["exposure_time"]) return NullStatus() - def update_parameters(self, angle_start, img_width, total_num_images, exposure_per_image): + def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): self.collection_params = { - "angle_start": angle_start, - "img_width": img_width, "total_num_images": total_num_images, - "exposure_per_image": exposure_per_image, + "start_angle": start_angle, + "scan_range": scan_range, + "exposure_time": exposure_time, } - def configure_detector(self, *args, **kwargs): - file_prefix = kwargs["file_prefix"] - data_directory_name = kwargs["data_directory_name"] + def configure_detector(self, file_prefix, data_directory_name): self.detector.file.external_name.put(file_prefix, wait=True) self.detector.file.write_path_template = data_directory_name From df2e7859f01e01d19ec2932fc68672360ab8fb08 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 16:45:40 -0400 Subject: [PATCH 066/234] [donotmerge] camera hijacking --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index d2037597..a92e1ece 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1051,7 +1051,7 @@ def createSampleTab(self): logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') _thread.start_new_thread(self.initVideo3, (0.25,)) - #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag self.timerSample = QTimer() self.timerSample.timeout.connect(self.timerSampleRefresh) From c41051c029058d09d37028592aabde8e390891f0 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 29 Aug 2023 16:46:30 -0400 Subject: [PATCH 067/234] Added md2 vector flyer --- daq_macros.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++--- md2_flyers.py | 31 ++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 21c85381..b8957de5 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3663,7 +3663,7 @@ def standardDaq(currentRequest): # arm the detector # perform governor and phase transitions # update flyer parameters - # flylogger.info("gathering vector parameters") + # fly x_beam = getPvDesc("beamCenterX") y_beam = getPvDesc("beamCenterY") wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) @@ -3717,10 +3717,65 @@ def vectorDaq(currentRequest): # collect all parameters # perform preparatory movements # arm the detector - # perform governor transitions + # perform governor and phase transitions # update flyer parameters # fly - pass + x_beam = getPvDesc("beamCenterX") + y_beam = getPvDesc("beamCenterY") + wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 + reqObj = currentRequest["request_obj"] + file_prefix = str(reqObj["file_prefix"]) + data_directory_name = str(reqObj["directory"]) + file_number_start = reqObj["file_number_start"] + sweep_start_angle = reqObj["sweep_start"] + sweep_end_angle = reqObj["sweep_end"] + exposure_time = reqObj["exposure_time"] + file_prefix = str(reqObj["file_prefix"]) + data_directory_name = str(reqObj["directory"]) + file_number_start = reqObj["file_number_start"] + img_width = reqObj["img_width"] + exposure_per_image = reqObj["exposure_time"] + total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + scan_range = float(total_num_images)*img_width + angle_start = sweep_start_angle + num_images_per_file = total_num_images + wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + + vector_params = reqObj["vectorParams"] + #x_vec_start=vector_params["vecStart"]["x"] + start_y=vector_params["vecStart"]["y"] + start_z=vector_params["vecStart"]["z"] + #x_vec_end=vector_params["vecEnd"]["x"] + stop_y=vector_params["vecEnd"]["y"] + stop_z=vector_params["vecEnd"]["z"] + + if flyer.detector.cam.armed.get() == 1: + daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' + 'should check the most recent collection to determine if it was successful. Cancelling' + 'this collection, retry when ready.') + logger.warning("Detector was in the armed state prior to this attempted collection.") + return 0 + start_time = time.time() + flyer.configure_detector(file_prefix, data_directory_name) + flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, + file_prefix, data_directory_name, file_number_start, x_beam, y_beam, + wavelength, det_distance_m, num_images_per_file) + def armed_callback(value, old_value, **kwargs): + return (old_value == 0 and value == 1) + arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) + flyer.detector.cam.acquire.put(1) + govStatus = gov_lib.setGovRobot(gov_robot, "DA") + arm_status.wait(timeout=20) + govStatus.wait(timeout=20) + logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") + if govStatus.exception(): + logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") + return + md2.phase.set(2) # TODO: Enum for MD2 phases and states + md2.ready_status().wait(timeout=20) + flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) + yield from bp.fly([flyer]) def clean_up_collection(): # this is a plan that should will always be run after a collection is complete diff --git a/md2_flyers.py b/md2_flyers.py index c4791b3b..d66e7563 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -17,7 +17,7 @@ class MD2StandardFlyer(): def __init__(self, md2, detector=None) -> None: - self.name = "MD2Flyer" + self.name = "MD2StandardFlyer" self.detector = detector self.md2 = md2 self.collection_params = {} @@ -106,4 +106,31 @@ def unstage(self): def collect_asset_docs(self): for _ in (): - yield _ \ No newline at end of file + yield _ + +class MD2VectorFlyer(MD2StandardFlyer): + def __init__(self, md2, detector=None) -> None: + super().__init__(md2, detector) + self.name = "MD2VectorFlyer" + + def kickoff(self): + # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z + self.md2.vector_scan(start_angle=self.collection_params["start_angle"], + scan_range=self.collection_params["scan_range"], + exposure_time=self.collection_params["exposure_time"], + start_y=self.collection_params["start_y"], + start_z=self.collection_params["start_z"], + stop_y=self.collection_params["stop_y"], + stop_z=self.collection_params["stop_z"],) + return NullStatus() + + def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z): + self.collection_params = { + "start_angle": start_angle, + "scan_range": scan_range, + "exposure_time": exposure_time, + "start_y": start_y, + "start_z": start_z, + "stop_y": stop_y, + "stop_z": stop_z, + } \ No newline at end of file From e5e0f292c10e3719a04ae53ce3240ed4d7307066 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 09:30:29 -0400 Subject: [PATCH 068/234] logging for testing --- daq_lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_lib.py b/daq_lib.py index bca75c1e..630d64e1 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -778,6 +778,7 @@ def center_on_click(x,y,fovx,fovy,source="screen",maglevel=0,jog=0,viewangle=daq #viewangle=daq_utils.CAMERA_ANGLE_BEAM, default camera angle is in-line with the beam if daq_utils.beamline == "nyx": + logger.info("center_on_click: %s" % str((x,y))) lsdc_x = daq_utils.screenPixX lsdc_y = daq_utils.screenPixY md2_x = getPvDesc("md2CenterPixelX") * 2 @@ -787,6 +788,7 @@ def center_on_click(x,y,fovx,fovy,source="screen",maglevel=0,jog=0,viewangle=daq x = x * scale_x y = y * scale_y str_coords = f'x: {x}, y: {y}' + logger.info(f'center_on_click: {str_coords}') setPvDesc("MD2C2C", str_coords) return From ba74ef2dc204e7f17466242f521dbab5199be941 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 09:36:38 -0400 Subject: [PATCH 069/234] change md2c2c target format --- daq_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_lib.py b/daq_lib.py index 630d64e1..372bbbd7 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -787,7 +787,7 @@ def center_on_click(x,y,fovx,fovy,source="screen",maglevel=0,jog=0,viewangle=daq scale_y = md2_y / lsdc_y x = x * scale_x y = y * scale_y - str_coords = f'x: {x}, y: {y}' + str_coords = f'{x} {y}' logger.info(f'center_on_click: {str_coords}') setPvDesc("MD2C2C", str_coords) return From bfe3249de8ce9638875348ac83dca26ada3a7255 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 10:31:02 -0400 Subject: [PATCH 070/234] 20 fps --- config_params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_params.py b/config_params.py index ce691135..423a7117 100644 --- a/config_params.py +++ b/config_params.py @@ -64,7 +64,7 @@ class OnMountAvailOptions(Enum): AUTO_RASTER = 2 # Mounts, centers and takes 2 orthogonal rasters HUTCH_TIMER_DELAY = 500 -SAMPLE_TIMER_DELAY = 0 +SAMPLE_TIMER_DELAY = 50 SERVER_CHECK_DELAY = 2000 FAST_DP_MIN_NODES = 4 From 6fd7250873104db53d5d282e56d3d8dd8d9e3bfe Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 10:33:25 -0400 Subject: [PATCH 071/234] restore camera a bit --- gui/control_main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index a92e1ece..d18146b4 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1050,7 +1050,6 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') - _thread.start_new_thread(self.initVideo3, (0.25,)) self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag self.timerSample = QTimer() From b96669706f8d6f13b08e0bc9cd4055fc978a0754 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 11:43:30 -0400 Subject: [PATCH 072/234] [donotmerge] testing standard collection --- beamline_support.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/beamline_support.py b/beamline_support.py index 021e3b7a..710f5729 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -4,6 +4,7 @@ import ophyd from ophyd import EpicsMotor from ophyd import EpicsScaler +from devices import MD2Positioner import time import epics import os @@ -321,7 +322,10 @@ def init_motors(): global motor_channel_dict for key in list(motor_dict.keys()): - motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) + if beamline_designation == "nyx" and ((key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): + motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) + else: + motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) def initControlPVs(): From bc2bc8018236cf45b99ce3902b08fb6701c5ea7d Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 12:04:41 -0400 Subject: [PATCH 073/234] fixes for standard collection --- beamline_support.py | 2 +- daq_macros.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beamline_support.py b/beamline_support.py index 710f5729..043ca7d5 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -322,7 +322,7 @@ def init_motors(): global motor_channel_dict for key in list(motor_dict.keys()): - if beamline_designation == "nyx" and ((key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): + if beamline_designation == "nyx" and ((key == "omega") or (key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) else: motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) diff --git a/daq_macros.py b/daq_macros.py index b8957de5..e46a45a0 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3777,7 +3777,7 @@ def armed_callback(value, old_value, **kwargs): flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([flyer]) -def clean_up_collection(): +def clean_up_collection(currentRequest): # this is a plan that should will always be run after a collection is complete yield from bps.mv(flyer.detector.cam.acquire, 0) if (lastOnSample()): From 985cc8c482dd8c83a39fe3351acf3a953f49bb2b Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 12:05:42 -0400 Subject: [PATCH 074/234] fix beamline designation --- beamline_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beamline_support.py b/beamline_support.py index 043ca7d5..ce4e17c6 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -322,7 +322,7 @@ def init_motors(): global motor_channel_dict for key in list(motor_dict.keys()): - if beamline_designation == "nyx" and ((key == "omega") or (key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): + if beamline_designation == "XF:19ID" and ((key == "omega") or (key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) else: motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) From 9755d6d66280d3a12b97a05613817b2a3932a4ac Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 13:08:57 -0400 Subject: [PATCH 075/234] use standard flyer --- start_bs.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/start_bs.py b/start_bs.py index ad83bb79..d5f6281b 100755 --- a/start_bs.py +++ b/start_bs.py @@ -184,13 +184,21 @@ class SampleXYZ(Device): mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] vdcm = VerticalDCM('XF:17IDA-OP:FMX{Mono:DCM', name='vdcm') + md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") + shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') + beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') + front_light = FrontLightDevice('XF:19IDC-ES{MD2}:', name='front_light') + aperature = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Aperature', name='aperature') + scintillator = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Scintillator', name='scintillator') + capillary = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Capillary', name='capillary') zebra = Zebra('XF:19IDC-ES{Zeb:1}:', name='zebra') from nyxtools.vector import VectorProgram vector = VectorProgram("XF:19IDC-ES{Gon:1-Vec}", name="vector") from mxtools.eiger import EigerSingleTriggerV26 detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) - from nyxtools.flyer_eiger2 import NYXEiger2Flyer - flyer = NYXEiger2Flyer(vector, zebra, detector) + #from nyxtools.flyer_eiger2 import NYXEiger2Flyer + from md2_flyers import MD2StandardFlyer + flyer = MD2StandardFlyer(md2, detector) from nyxtools.nyx_raster_flyer import NYXRasterFlyer raster_flyer = NYXRasterFlyer(vector, zebra, detector) @@ -201,13 +209,6 @@ class SampleXYZ(Device): govs = _make_governors("XF:19IDC-ES", name="govs") gov_robot = govs.gov.Robot - md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") - shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') - beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') - front_light = FrontLightDevice('XF:19IDC-ES{MD2}:', name='front_light') - aperature = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Aperature', name='aperature') - scintillator = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Scintillator', name='scintillator') - capillary = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Capillary', name='capillary') back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") From 267ddf2c76946e6490361231b7a61e7fad9b237b Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 13:29:02 -0400 Subject: [PATCH 076/234] vivew spelling --- daq_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_utils.py b/daq_utils.py index 3201673f..cd70b7c6 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -34,7 +34,7 @@ CAMERA_ANGLE_BEAM = 0 # viewing angle is in line with beam, upstream from the sample, facing downstream, top toward ceiling CAMERA_ANGLE_ABOVE = 1 # viewing angle is directly above sample facing downward, top of view is downstream CAMERA_ANGLE_BELOW = 2 # viewing angle is directly below sample facing upward, top of view is downstream -global mag1ViewAngle, mag2ViewAngle, mag3VivewAngle, mag4ViewAngle +global mag1ViewAngle, mag2ViewAngle, mag3ViewAngle, mag4ViewAngle mag1ViewAngle = CAMERA_ANGLE_BEAM mag2ViewAngle = CAMERA_ANGLE_BEAM mag3ViewAngle = CAMERA_ANGLE_BEAM From 07ef86ddeb089ef8b7085c3a0a41132abadb4f65 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 13:45:25 -0400 Subject: [PATCH 077/234] update md2 status check --- devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.py b/devices.py index 0efd1bd1..f6d8fbc1 100644 --- a/devices.py +++ b/devices.py @@ -154,7 +154,7 @@ def ready_status(self): # returns an ophyd status object that monitors the state pv for operations to complete def check_ready(*, old_value, value, **kwargs): "Return True when the MD2 is ready" - return (old_value == 3 and value == 2) + return (old_value == 7 and value == 4) return SubscriptionStatus(self.state, check_ready) def standard_scan(self, From e6c4485e97b3724b861e573cdd2e7511fe7a958e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 14:09:34 -0400 Subject: [PATCH 078/234] update ready check to just check status --- devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.py b/devices.py index f6d8fbc1..c251c420 100644 --- a/devices.py +++ b/devices.py @@ -154,7 +154,7 @@ def ready_status(self): # returns an ophyd status object that monitors the state pv for operations to complete def check_ready(*, old_value, value, **kwargs): "Return True when the MD2 is ready" - return (old_value == 7 and value == 4) + return (value == 4) return SubscriptionStatus(self.state, check_ready) def standard_scan(self, From 4d21bb6f66ec52faa7939dca63552ebb15067ec9 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 14:14:23 -0400 Subject: [PATCH 079/234] update wrapper to perform phase transition --- daq_macros.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index e46a45a0..8cbf0b7b 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3773,7 +3773,7 @@ def armed_callback(value, old_value, **kwargs): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return md2.phase.set(2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=20) + md2.ready_status().wait(timeout=10) flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([flyer]) @@ -3783,6 +3783,8 @@ def clean_up_collection(currentRequest): if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait() + md2.phase.set(1) + md2.ready_status().wait(timeout=10) def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 86bce7a6a5e5016d594c8986da1cc2a174d6b448 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 14:31:56 -0400 Subject: [PATCH 080/234] phase indexes all off by 1 --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 8cbf0b7b..10cc35f9 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3783,7 +3783,7 @@ def clean_up_collection(currentRequest): if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait() - md2.phase.set(1) + md2.phase.set(0) md2.ready_status().wait(timeout=10) def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 5b89c6b366f94caeeeaa66ab8a114ef2b5ffa894 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 14:35:39 -0400 Subject: [PATCH 081/234] cast port to int --- devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.py b/devices.py index c251c420..4da21656 100644 --- a/devices.py +++ b/devices.py @@ -148,7 +148,7 @@ class MD2Device(GonioDevice): phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') - exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=os.environ['EXPORTER_PORT'], name='exporter') + exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=int(os.environ['EXPORTER_PORT']), name='exporter') def ready_status(self): # returns an ophyd status object that monitors the state pv for operations to complete From aa5dcb40ceb2b33f8ac32bf32aa7a229815ce819 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 14:48:44 -0400 Subject: [PATCH 082/234] use vector flyer for vector collections --- daq_macros.py | 14 +++++++------- start_bs.py | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 10cc35f9..3e942652 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3750,21 +3750,21 @@ def vectorDaq(currentRequest): stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] - if flyer.detector.cam.armed.get() == 1: + if vector_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' 'this collection, retry when ready.') logger.warning("Detector was in the armed state prior to this attempted collection.") return 0 start_time = time.time() - flyer.configure_detector(file_prefix, data_directory_name) - flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, + vector_flyer.configure_detector(file_prefix, data_directory_name) + vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) - arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) - flyer.detector.cam.acquire.put(1) + arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) + vector_flyer.detector.cam.acquire.put(1) govStatus = gov_lib.setGovRobot(gov_robot, "DA") arm_status.wait(timeout=20) govStatus.wait(timeout=20) @@ -3774,8 +3774,8 @@ def armed_callback(value, old_value, **kwargs): return md2.phase.set(2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) - flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) - yield from bp.fly([flyer]) + vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) + yield from bp.fly([vector_flyer]) def clean_up_collection(currentRequest): # this is a plan that should will always be run after a collection is complete diff --git a/start_bs.py b/start_bs.py index d5f6281b..7aaa6649 100755 --- a/start_bs.py +++ b/start_bs.py @@ -197,8 +197,9 @@ class SampleXYZ(Device): from mxtools.eiger import EigerSingleTriggerV26 detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) #from nyxtools.flyer_eiger2 import NYXEiger2Flyer - from md2_flyers import MD2StandardFlyer + from md2_flyers import MD2StandardFlyer, MD2VectorFlyer flyer = MD2StandardFlyer(md2, detector) + vector_flyer = MD2VectorFlyer(md2, detector) from nyxtools.nyx_raster_flyer import NYXRasterFlyer raster_flyer = NYXRasterFlyer(vector, zebra, detector) From deae5ccef2216b5154d61f653ac289f726948ae3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:06:02 -0400 Subject: [PATCH 083/234] do not use centering table for scan --- devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.py b/devices.py index 4da21656..30d7fe31 100644 --- a/devices.py +++ b/devices.py @@ -228,7 +228,7 @@ def raster_scan(self, frames_per_line=5, # int: number of frames on the horizontal range. exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. + use_table_centering=False, # boolean: true to use the centring table to do the pitch movements. use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). ): From d940c73c526b7f6b5c97942f6ab98b52bcad79ee Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:14:45 -0400 Subject: [PATCH 084/234] timeout changes for phase transitions --- daq_macros.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 3e942652..7c81295e 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3766,14 +3766,14 @@ def armed_callback(value, old_value, **kwargs): arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) vector_flyer.detector.cam.acquire.put(1) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait(timeout=20) + arm_status.wait(timeout=10) govStatus.wait(timeout=20) logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return md2.phase.set(2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=10) + md2.ready_status().wait(timeout=20) vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) @@ -3782,9 +3782,9 @@ def clean_up_collection(currentRequest): yield from bps.mv(flyer.detector.cam.acquire, 0) if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) - gov_status.wait() + gov_status.wait(timeout=30) md2.phase.set(0) - md2.ready_status().wait(timeout=10) + md2.ready_status().wait(timeout=30) def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From cbd8703f6016dd60ea034e650ab7e659a44c592c Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:22:55 -0400 Subject: [PATCH 085/234] added phase transition function --- devices.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/devices.py b/devices.py index 30d7fe31..8b4c2df8 100644 --- a/devices.py +++ b/devices.py @@ -156,6 +156,14 @@ def check_ready(*, old_value, value, **kwargs): "Return True when the MD2 is ready" return (value == 4) return SubscriptionStatus(self.state, check_ready) + + def phase_transition(self, phase): + # returns an ophyd status object that monitors the phase pv for operations to complete + self.phase.put(phase) + def check_transition_state(*, old_value, value, **kwargs): + "Return True when the MD2 is ready" + return old_value == 7 and value == 4 and self.phase.get() == phase + return SubscriptionStatus(self.state, check_transition_state) def standard_scan(self, frame_number=0, # int: frame ID just for logging purposes. From 0432751bf2776702765b9e52f9eec55ff5498ec2 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:24:47 -0400 Subject: [PATCH 086/234] uses safer phase transition function --- daq_macros.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 7c81295e..d7c7368b 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3708,8 +3708,7 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return - md2.phase.set(2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=20) + md2.phase_transition(2).wait(timeout=20) flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3772,8 +3771,7 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return - md2.phase.set(2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=20) + md2.phase_transition(2).wait(timeout=20) vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) @@ -3783,8 +3781,7 @@ def clean_up_collection(currentRequest): if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) - md2.phase.set(0) - md2.ready_status().wait(timeout=30) + md2.phase_transition(0).wait(timeout=20) def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 230a990ee55a5155a4f8a7f87486b14d04780368 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:29:53 -0400 Subject: [PATCH 087/234] remove old val --- devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.py b/devices.py index 8b4c2df8..f6c6b53f 100644 --- a/devices.py +++ b/devices.py @@ -162,7 +162,7 @@ def phase_transition(self, phase): self.phase.put(phase) def check_transition_state(*, old_value, value, **kwargs): "Return True when the MD2 is ready" - return old_value == 7 and value == 4 and self.phase.get() == phase + return (value == 4 and self.phase.get() == phase) return SubscriptionStatus(self.state, check_transition_state) def standard_scan(self, From eaf48ca341883c422fc1f3164a31e6535c57ba17 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:33:08 -0400 Subject: [PATCH 088/234] Revert "uses safer phase transition function" This reverts commit de8bd73f8420efd0be6b3a0001eb4b931c22d5ab. --- daq_macros.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index d7c7368b..7c81295e 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3708,7 +3708,8 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return - md2.phase_transition(2).wait(timeout=20) + md2.phase.set(2) # TODO: Enum for MD2 phases and states + md2.ready_status().wait(timeout=20) flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3771,7 +3772,8 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return - md2.phase_transition(2).wait(timeout=20) + md2.phase.set(2) # TODO: Enum for MD2 phases and states + md2.ready_status().wait(timeout=20) vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) @@ -3781,7 +3783,8 @@ def clean_up_collection(currentRequest): if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) - md2.phase_transition(0).wait(timeout=20) + md2.phase.set(0) + md2.ready_status().wait(timeout=30) def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 2ad057df0273d26b8f34e023e47036833204aa91 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 15:38:38 -0400 Subject: [PATCH 089/234] added time logging for transitions --- daq_macros.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index 7c81295e..07aec22f 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3708,8 +3708,10 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return + start_time = time.time() md2.phase.set(2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=20) + logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3772,19 +3774,23 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return + start_time = time.time() md2.phase.set(2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=20) + logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) def clean_up_collection(currentRequest): # this is a plan that should will always be run after a collection is complete + start_time = time.time() yield from bps.mv(flyer.detector.cam.acquire, 0) if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) md2.phase.set(0) md2.ready_status().wait(timeout=30) + logger.info(f"clean_up took {time.time()-start_time} seconds.") def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 3472c2c4bd4d87dfe76b5ad4f29ca10db1670d84 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 16:14:42 -0400 Subject: [PATCH 090/234] use bps to make phase transitions --- daq_macros.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 07aec22f..ccf4b755 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3709,7 +3709,7 @@ def armed_callback(value, old_value, **kwargs): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return start_time = time.time() - md2.phase.set(2) # TODO: Enum for MD2 phases and states + yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=20) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) @@ -3775,7 +3775,7 @@ def armed_callback(value, old_value, **kwargs): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return start_time = time.time() - md2.phase.set(2) # TODO: Enum for MD2 phases and states + yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=20) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) @@ -3788,7 +3788,7 @@ def clean_up_collection(currentRequest): if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) - md2.phase.set(0) + yield from bps.mv(md2.phase, 0) md2.ready_status().wait(timeout=30) logger.info(f"clean_up took {time.time()-start_time} seconds.") From 4c55bade571917a13000bf9dd507abd4f2466579 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 16:28:43 -0400 Subject: [PATCH 091/234] propagate md2 response --- md2_flyers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index d66e7563..c0bacda8 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -32,10 +32,11 @@ def __init__(self, md2, detector=None) -> None: self._collection_dictionary = None def kickoff(self): - self.md2.standard_scan(num_images=self.collection_params["total_num_images"], + md2_msg = self.md2.standard_scan(num_images=self.collection_params["total_num_images"], start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["scan_range"], exposure_time=self.collection_params["exposure_time"]) + logger.info(f"md2_msg: {md2_msg}") return NullStatus() def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): @@ -115,13 +116,14 @@ def __init__(self, md2, detector=None) -> None: def kickoff(self): # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z - self.md2.vector_scan(start_angle=self.collection_params["start_angle"], + md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["scan_range"], exposure_time=self.collection_params["exposure_time"], start_y=self.collection_params["start_y"], start_z=self.collection_params["start_z"], stop_y=self.collection_params["stop_y"], stop_z=self.collection_params["stop_z"],) + logger.info(f"md2_msg: {md2_msg}") return NullStatus() def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z): From adca991202ed5c81359881289f7563a522e2b1e6 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 16:41:43 -0400 Subject: [PATCH 092/234] debugging eiger metadata --- daq_macros.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index ccf4b755..211d949a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3693,7 +3693,9 @@ def standardDaq(currentRequest): logger.warning("Detector was in the armed state prior to this attempted collection.") return 0 start_time = time.time() + logger.info(f"Configuring detector for standard collection with file_prefix {file_prefix} and data_directory_name {data_directory_name}") flyer.configure_detector(file_prefix, data_directory_name) + logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}, num_images_per_file {num_images_per_file}") flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) From 001ab980061d423ce2d31750ab55a04c1a6afb61 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 16:59:52 -0400 Subject: [PATCH 093/234] removing wait from detector arming --- md2_flyers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index c0bacda8..1c45e619 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -48,7 +48,7 @@ def update_parameters(self, total_num_images, start_angle, scan_range, exposure_ } def configure_detector(self, file_prefix, data_directory_name): - self.detector.file.external_name.put(file_prefix, wait=True) + self.detector.file.external_name.put(file_prefix) self.detector.file.write_path_template = data_directory_name def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_image, @@ -63,9 +63,9 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im self.detector.cam.acquire_time.put(exposure_per_image) self.detector.cam.acquire_period.put(exposure_per_image) self.detector.cam.num_triggers.put(total_num_images) - self.detector.cam.file_path.put(data_directory_name, wait=True) - self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id", wait=True) - self.detector.cam.sequence_id.put(file_number_start, wait=True) + self.detector.cam.file_path.put(data_directory_name) + self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") + self.detector.cam.sequence_id.put(file_number_start) self.detector.cam.beam_center_x.put(x_beam) self.detector.cam.beam_center_y.put(y_beam) self.detector.cam.omega_incr.put(img_width) @@ -75,7 +75,7 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im self.detector.cam.trigger_mode.put( EXTERNAL_ENABLE ) # must be external_enable to get the correct number of triggers and stop acquire - self.detector.file.file_write_images_per_file.put(num_images_per_file, wait=True) + self.detector.file.file_write_images_per_file.put(num_images_per_file) def armed_callback(value, old_value, **kwargs): if old_value == 0 and value == 1: From 1256cd17b1396a4ea7e8922d8595e9c2dd91bde7 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 30 Aug 2023 18:15:25 -0400 Subject: [PATCH 094/234] critical detector staging --- daq_macros.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index 211d949a..51584b58 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3695,6 +3695,7 @@ def standardDaq(currentRequest): start_time = time.time() logger.info(f"Configuring detector for standard collection with file_prefix {file_prefix} and data_directory_name {data_directory_name}") flyer.configure_detector(file_prefix, data_directory_name) + flyer.detector.stage() logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}, num_images_per_file {num_images_per_file}") flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, @@ -3787,6 +3788,7 @@ def clean_up_collection(currentRequest): # this is a plan that should will always be run after a collection is complete start_time = time.time() yield from bps.mv(flyer.detector.cam.acquire, 0) + flyer.detector.unstage() if (lastOnSample()): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) From 76d797a110708313a5b828b910cf9e7ac993b7ba Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 09:48:22 -0400 Subject: [PATCH 095/234] more detector staging, reduced timeouts for tests --- daq_macros.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 51584b58..326be2b4 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3695,11 +3695,11 @@ def standardDaq(currentRequest): start_time = time.time() logger.info(f"Configuring detector for standard collection with file_prefix {file_prefix} and data_directory_name {data_directory_name}") flyer.configure_detector(file_prefix, data_directory_name) - flyer.detector.stage() logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}, num_images_per_file {num_images_per_file}") flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) + flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) @@ -3713,7 +3713,7 @@ def armed_callback(value, old_value, **kwargs): return start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=20) + md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3766,6 +3766,7 @@ def vectorDaq(currentRequest): vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) + flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) @@ -3779,7 +3780,7 @@ def armed_callback(value, old_value, **kwargs): return start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=20) + md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) @@ -3793,7 +3794,7 @@ def clean_up_collection(currentRequest): gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) gov_status.wait(timeout=30) yield from bps.mv(md2.phase, 0) - md2.ready_status().wait(timeout=30) + md2.ready_status().wait(timeout= 20) logger.info(f"clean_up took {time.time()-start_time} seconds.") def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 997580bf27a494057f12e99f438f35dc2fbff8ab Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:00:29 -0400 Subject: [PATCH 096/234] add zoom combo box --- devices.py | 11 ++++++++++- gui/control_main.py | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/devices.py b/devices.py index f6c6b53f..98be1859 100644 --- a/devices.py +++ b/devices.py @@ -276,4 +276,13 @@ def open_shutter(self): self.control.set(1)#self.pos_opn.get()) iocs are down, so just setting it to 1 def close_shutter(self): - self.control.set(0)#self.pos_cls.get()) \ No newline at end of file + self.control.set(0)#self.pos_cls.get()) + +class CameraDevice(Device): + # MD2 camera has the following attributes: + # CoaxCamScaleX + # CoaxCamScaleY + # CoaxialCameraZoomValue + scale_x = Cpt(EpicsSignalRO, 'CoaxCamScaleX', name='scale_x') + scale_y = Cpt(EpicsSignalRO, 'CoaxCamScaleY', name='scale_y') + zoom = Cpt(EpicsSignal, 'CoaxialCameraZoomValue', name='zoom') diff --git a/gui/control_main.py b/gui/control_main.py index d18146b4..fb37f720 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -20,7 +20,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice +from devices import GonioDevice, CameraDevice import daq_utils import db_lib @@ -1124,6 +1124,18 @@ def createSampleTab(self): self.zoomRadioGroup.addButton(self.zoom3Radio) if daq_utils.sampleCameraCount >= 4: self.zoomRadioGroup.addButton(self.zoom4Radio) + else: + self.zoomLevelComboBox = QtWidgets.QComboBox(self) + self.zoomLevelComboBox.addItems(["1","2","3","4","5","6","7"]) + self.zoomLevelComboBox.activated[str].connect(self.zoomLevelComboActivatedCB) + self.zoom1Radio.hide() + self.zoom2Radio.hide() + self.zoom3Radio.hide() + self.zoom4Radio.hide() + hBoxZoomLevelLayout = QtWidgets.QHBoxLayout() + hBoxZoomLevelLayout.addWidget(self.zoomLevelComboBox) + + beamOverlayPen = QtGui.QPen(QtCore.Qt.red) self.tempBeamSizeXMicrons = 30 self.tempBeamSizeYMicrons = 30 @@ -1271,6 +1283,8 @@ def createSampleTab(self): hBoxVidControlLayout.addWidget(self.zoom3Radio) if daq_utils.sampleCameraCount >= 4: hBoxVidControlLayout.addWidget(self.zoom4Radio) + else: + hBoxVidControlLayout.addLayout(hBoxZoomLevelLayout) hBoxVidControlLayout.addWidget(focusLabel) hBoxVidControlLayout.addWidget(focusPlusButton) hBoxVidControlLayout.addWidget(focusMinusButton) @@ -1685,6 +1699,9 @@ def flushBuffer(self, vidStream): if commTime > 0.01: return + def zoomLevelComboActivatedCB(self, identifier): + self.camera.zoom.set(identifier) + def zoomLevelToggledCB(self, identifier): fov = {} zoomedCursorX = daq_utils.screenPixCenterX - self.centerMarkerCharOffsetX @@ -4838,6 +4855,7 @@ def pauseButtonStateCB(self, value=None, char_value=None, **kw): def initOphyd(self): self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") + self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") def initUI(self): self.tabs = QtWidgets.QTabWidget() From fcea706bd0d794102a137761de1f415266429e78 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:07:33 -0400 Subject: [PATCH 097/234] put does not crash when issued rapidly --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index fb37f720..c6eca6b0 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1700,7 +1700,7 @@ def flushBuffer(self, vidStream): return def zoomLevelComboActivatedCB(self, identifier): - self.camera.zoom.set(identifier) + self.camera.zoom.put(identifier) def zoomLevelToggledCB(self, identifier): fov = {} From 031eff2c6ae3da76963551b0e3d34229e36abc58 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:21:29 -0400 Subject: [PATCH 098/234] universalize light device --- devices.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devices.py b/devices.py index 98be1859..31de2d07 100644 --- a/devices.py +++ b/devices.py @@ -95,10 +95,10 @@ def process_ret(self, ret): elif "EVT:" in line: pass #return self.process_evt(line) -class FrontLightDevice(Device): - control = Cpt(EpicsSignal, 'FrontLightIsOn', name='control') - factor = Cpt(EpicsSignal, 'FrontLightFactor', name='factor') - level = Cpt(EpicsSignal, 'FrontLightLevel', name='level') +class LightDevice(Device): + control = Cpt(EpicsSignal, 'LightIsOn', name='control') + factor = Cpt(EpicsSignal, 'LightFactor', name='factor') + level = Cpt(EpicsSignal, 'LightLevel', name='level') def is_on(self): return self.control.get() == 1 From 279cdc5c37bf09877dafc736816a95b9246413fc Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:35:16 -0400 Subject: [PATCH 099/234] move trigger mode set --- md2_flyers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 1c45e619..e451880f 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -62,6 +62,10 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im file_prefix_minus_directory = file_prefix_minus_directory.split("/")[-1] self.detector.cam.acquire_time.put(exposure_per_image) self.detector.cam.acquire_period.put(exposure_per_image) + self.detector.cam.trigger_mode.put( + EXTERNAL_ENABLE + ) # must be external_enable to get the correct number of triggers and stop acquire + self.detector.cam.num_images.put(total_num_images) self.detector.cam.num_triggers.put(total_num_images) self.detector.cam.file_path.put(data_directory_name) self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") @@ -72,9 +76,6 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im self.detector.cam.omega_start.put(angle_start) self.detector.cam.wavelength.put(wavelength) self.detector.cam.det_distance.put(det_distance_m) - self.detector.cam.trigger_mode.put( - EXTERNAL_ENABLE - ) # must be external_enable to get the correct number of triggers and stop acquire self.detector.file.file_write_images_per_file.put(num_images_per_file) def armed_callback(value, old_value, **kwargs): From a038f7002d769dab63e966c5b99f7bb01ea078dd Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:36:46 -0400 Subject: [PATCH 100/234] move detector staging --- daq_macros.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 326be2b4..1677734a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3699,7 +3699,6 @@ def standardDaq(currentRequest): flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) - flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) @@ -3714,6 +3713,7 @@ def armed_callback(value, old_value, **kwargs): start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) + flyer.detector.stage() logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3766,7 +3766,6 @@ def vectorDaq(currentRequest): vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) - flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) @@ -3781,6 +3780,7 @@ def armed_callback(value, old_value, **kwargs): start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) + flyer.detector.stage() logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) From e42b95aca2775eff1ee0bdc09e74c3a8fbaee8f2 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 11:40:04 -0400 Subject: [PATCH 101/234] back and front lights use lightdevice --- start_bs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/start_bs.py b/start_bs.py index 7aaa6649..5b2d2680 100755 --- a/start_bs.py +++ b/start_bs.py @@ -8,7 +8,7 @@ import os from mxtools.governor import _make_governors from ophyd.signal import EpicsSignalBase -from devices import FrontLightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice +from devices import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, TopAlignerFast, TopAlignerSlow, GoniometerStack) @@ -187,7 +187,8 @@ class SampleXYZ(Device): md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') - front_light = FrontLightDevice('XF:19IDC-ES{MD2}:', name='front_light') + front_light = LightDevice('XF:19IDC-ES{MD2}:Front', name='front_light') + back_light = LightDevice('XF:19IDC-ES{MD2}:Back', name='back_light') aperature = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Aperature', name='aperature') scintillator = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Scintillator', name='scintillator') capillary = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Capillary', name='capillary') @@ -211,7 +212,7 @@ class SampleXYZ(Device): gov_robot = govs.gov.Robot - back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") + #back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") back_light_range = (back_light_low_limit.get(), back_light_high_limit.get()) From b33ae54dc42f2ac22c5b1231cc268bca7b19d523 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 13:56:46 -0400 Subject: [PATCH 102/234] exporter 3-click-centering --- daq_utils.py | 1 + gui/control_main.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/daq_utils.py b/daq_utils.py index cd70b7c6..1b83350d 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -72,6 +72,7 @@ def init_environment(): highMagPixY = float(getBlConfig("highMagPixY")) screenPixX = float(getBlConfig("screenPixX")) screenPixY = float(getBlConfig("screenPixY")) + exporter_enabled = bool(getBlConfig("exporterEnabled")) try: unitScaling = float(getBlConfig("unitScaling")) diff --git a/gui/control_main.py b/gui/control_main.py index c6eca6b0..db9df03f 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -20,7 +20,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice, CameraDevice +from devices import GonioDevice, CameraDevice, MD2Device, LightDevice import daq_utils import db_lib @@ -3037,7 +3037,10 @@ def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 self.click3Button.setStyleSheet("background-color: yellow") - self.send_to_server("mvaDescriptor", ["omega", 0]) + if(daq_utils.exporter_enabled): + self.md2.exporter.cmd("startManualSampleCentering") + else: + self.send_to_server("mvaDescriptor", ["omega", 0]) def fillPolyRaster( self, rasterReq @@ -3704,7 +3707,10 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 - comm_s = ( + if daq_utils.exporter_enabled: + self.md2.exporter.cmd("setCenteringClick", (correctedC2C_x, correctedC2C_y)) + else: + comm_s = ( "center_on_click", [ correctedC2C_x, @@ -4854,8 +4860,14 @@ def pauseButtonStateCB(self, value=None, char_value=None, **kw): self.pauseButtonStateSignal.emit(pauseButtonStateVar) def initOphyd(self): - self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") - self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") + if daq_utils.beamline == "nyx": + self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") + self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") + self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") + self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") + self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") + else: + pass def initUI(self): self.tabs = QtWidgets.QTabWidget() From b73b835b457cf665b4a4e10ab713ee40d61465b1 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:02:37 -0400 Subject: [PATCH 103/234] 3-click centering fixes --- daq_utils.py | 2 +- gui/control_main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_utils.py b/daq_utils.py index 1b83350d..8733a17c 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -51,7 +51,7 @@ def setBlConfig(param, value, beamline=beamline): db_lib.setBeamlineConfigParam(beamline, param, value) def init_environment(): - global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle + global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle,exporter_enabled owner = getpass.getuser() diff --git a/gui/control_main.py b/gui/control_main.py index db9df03f..d3fd827c 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3038,7 +3038,7 @@ def center3LoopCB(self): self.threeClickCount = 1 self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): - self.md2.exporter.cmd("startManualSampleCentering") + self.md2.exporter.cmd("startManualSampleCentering", "") else: self.send_to_server("mvaDescriptor", ["omega", 0]) From cebf7cd9f3d92d346733223ba60902340f20cce6 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:05:52 -0400 Subject: [PATCH 104/234] parameter space separation --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index d3fd827c..4dcd14f8 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3708,7 +3708,7 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - self.md2.exporter.cmd("setCenteringClick", (correctedC2C_x, correctedC2C_y)) + self.md2.exporter.cmd("setCenteringClick", f"{correctedC2C_x} {correctedC2C_y}") else: comm_s = ( "center_on_click", From e44877f180e60c1c8d40654c333559dfb1e38911 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:08:41 -0400 Subject: [PATCH 105/234] prevents comm_s from being sent during 3CC --- gui/control_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/control_main.py b/gui/control_main.py index 4dcd14f8..4f54f83d 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3709,6 +3709,7 @@ def pixelSelect(self, event): self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: self.md2.exporter.cmd("setCenteringClick", f"{correctedC2C_x} {correctedC2C_y}") + return else: comm_s = ( "center_on_click", From f66e4ee4c26f305f137ac5640597a6417f125900 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:10:37 -0400 Subject: [PATCH 106/234] britain mispells centering so now we need another commit --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 4f54f83d..4f78c43c 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3038,7 +3038,7 @@ def center3LoopCB(self): self.threeClickCount = 1 self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): - self.md2.exporter.cmd("startManualSampleCentering", "") + self.md2.exporter.cmd("startManualSampleCentring", "") else: self.send_to_server("mvaDescriptor", ["omega", 0]) @@ -3708,7 +3708,7 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - self.md2.exporter.cmd("setCenteringClick", f"{correctedC2C_x} {correctedC2C_y}") + self.md2.exporter.cmd("setCentringClick", f"{correctedC2C_x} {correctedC2C_y}") return else: comm_s = ( From 723b9a2ecadc7078c9ed61799107e3c8fde2de4f Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:12:02 -0400 Subject: [PATCH 107/234] s --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 4f78c43c..b3dd1bee 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3708,7 +3708,7 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - self.md2.exporter.cmd("setCentringClick", f"{correctedC2C_x} {correctedC2C_y}") + self.md2.exporter.cmd("setCentringClicks", f"{correctedC2C_x} {correctedC2C_y}") return else: comm_s = ( From 06ee9ad031ebaa141a1a65b41d85d8497ff2cce9 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:14:15 -0400 Subject: [PATCH 108/234] Revert "s" This reverts commit 428b05c179fab1deeb636805cdf6a2e76d2e6ddd. --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index b3dd1bee..4f78c43c 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3708,7 +3708,7 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - self.md2.exporter.cmd("setCentringClicks", f"{correctedC2C_x} {correctedC2C_y}") + self.md2.exporter.cmd("setCentringClick", f"{correctedC2C_x} {correctedC2C_y}") return else: comm_s = ( From 062d3dc2a44a05ec81f38f2a1e28a2c002434b8c Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:21:35 -0400 Subject: [PATCH 109/234] det stage testing --- daq_macros.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 1677734a..326be2b4 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3699,6 +3699,7 @@ def standardDaq(currentRequest): flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) + flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) @@ -3713,7 +3714,6 @@ def armed_callback(value, old_value, **kwargs): start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) - flyer.detector.stage() logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) yield from bp.fly([flyer]) @@ -3766,6 +3766,7 @@ def vectorDaq(currentRequest): vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) + flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) @@ -3780,7 +3781,6 @@ def armed_callback(value, old_value, **kwargs): start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) - flyer.detector.stage() logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) From c69a303dba31c7b7a070ea7f6c3b45f96f2dcc5e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:26:51 -0400 Subject: [PATCH 110/234] 3CC changes and new beam center signals --- devices.py | 2 ++ gui/control_main.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/devices.py b/devices.py index 31de2d07..6a262ca1 100644 --- a/devices.py +++ b/devices.py @@ -143,6 +143,8 @@ class MD2Device(GonioDevice): # TODO: Enum for MD2 phases and states cx = Cpt(MD2Positioner, 'CentringX',name='cx') cy = Cpt(MD2Positioner, 'CentringY',name='cy') + center_pixel_x = Cpt(EpicsSignalRO, 'BeamPositionHorizontal',name='center_pixel_x') + center_pixel_y = Cpt(EpicsSignalRO, 'BeamPositionVertical',name='center_pixel_y') state = Cpt(EpicsSignalRO, 'State',name='state') phase = Cpt(EpicsSignal, 'CurrentPhase',name='phase') phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') diff --git a/gui/control_main.py b/gui/control_main.py index 4f78c43c..1aac8f50 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3707,8 +3707,16 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 - if daq_utils.exporter_enabled: - self.md2.exporter.cmd("setCentringClick", f"{correctedC2C_x} {correctedC2C_y}") + if daq_utils.exporter_enabled: + lsdc_x = daq_utils.screenPixX + lsdc_y = daq_utils.screenPixY + md2_x = self.md2.center_pixel_x.get() * 2 + md2_y = self.md2.center_pixel_y.get() * 2 + scale_x = md2_x / lsdc_x + scale_y = md2_y / lsdc_y + correctedC2C_x = correctedC2C_x * scale_x + correctedC2C_y = correctedC2C_y * scale_y + self.md2.exporter.cmd("setCentringClick", f"0 {correctedC2C_x} {correctedC2C_y}") return else: comm_s = ( From 8136df363900cec32a079bb989a464e5b66e710b Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:36:23 -0400 Subject: [PATCH 111/234] using PV for 3CC instead of exporter --- devices.py | 1 + gui/control_main.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/devices.py b/devices.py index 6a262ca1..f9d78c37 100644 --- a/devices.py +++ b/devices.py @@ -145,6 +145,7 @@ class MD2Device(GonioDevice): cy = Cpt(MD2Positioner, 'CentringY',name='cy') center_pixel_x = Cpt(EpicsSignalRO, 'BeamPositionHorizontal',name='center_pixel_x') center_pixel_y = Cpt(EpicsSignalRO, 'BeamPositionVertical',name='center_pixel_y') + centring_click = Cpt(EpicsSignal, 'setCentringClick',name='centring_click') state = Cpt(EpicsSignalRO, 'State',name='state') phase = Cpt(EpicsSignal, 'CurrentPhase',name='phase') phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') diff --git a/gui/control_main.py b/gui/control_main.py index 1aac8f50..55e921ed 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3716,7 +3716,7 @@ def pixelSelect(self, event): scale_y = md2_y / lsdc_y correctedC2C_x = correctedC2C_x * scale_x correctedC2C_y = correctedC2C_y * scale_y - self.md2.exporter.cmd("setCentringClick", f"0 {correctedC2C_x} {correctedC2C_y}") + self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") return else: comm_s = ( From 3badba0e58a985a8388ba71526ec1bae8c33bd3e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 14:39:21 -0400 Subject: [PATCH 112/234] ensure 3cc mode ends --- gui/control_main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index 55e921ed..fa5f6ee2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3717,6 +3717,9 @@ def pixelSelect(self, event): correctedC2C_x = correctedC2C_x * scale_x correctedC2C_y = correctedC2C_y * scale_y self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") + if self.threeClickCount == 4: + self.threeClickCount = 0 + self.click3Button.setStyleSheet("background-color: None") return else: comm_s = ( From 8a5ce24cd71962807eb8525630671b29d964cc60 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 15:00:12 -0400 Subject: [PATCH 113/234] trying to solve missing metadata --- md2_flyers.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index e451880f..646411e6 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -106,9 +106,79 @@ def unstage(self): logger.debug("flyer unstaging") self.collection_params = {} + def read_configuration(self): + return {} + + def describe_configuration(self): + return {} + + # def collect_asset_docs(self): + # for _ in (): + # yield _ + def collect_asset_docs(self): - for _ in (): - yield _ + asset_docs_cache = [] + + # Get the Resource which was produced when the detector was staged. + ((name, resource),) = self.detector.file.collect_asset_docs() + + asset_docs_cache.append(("resource", resource)) + self._datum_ids = DEFAULT_DATUM_DICT + # Generate Datum documents from scratch here, because the detector was + # triggered externally by the DeltaTau, never by ophyd. + resource_uid = resource["uid"] + # num_points = int(math.ceil(self.detector.cam.num_images.get() / + # self.detector.cam.fw_num_images_per_file.get())) + + # We are currently generating only one datum document for all frames, that's why + # we use the 0th index below. + # + # Uncomment & update the line below if more datum documents are needed: + # for i in range(num_points): + + seq_id = self.detector.cam.sequence_id.get() + + self._master_file = f"{resource['root']}/{resource['resource_path']}_{seq_id}_master.h5" + if not os.path.isfile(self._master_file): + raise RuntimeError(f"File {self._master_file} does not exist") + + # The pseudocode below is from Tom Caswell explaining the relationship between resource, datum, and events. + # + # resource = { + # "resource_id": "RES", + # "resource_kwargs": {}, # this goes to __init__ + # "spec": "AD-EIGER-MX", + # ...: ..., + # } + # datum = { + # "datum_id": "a", + # "datum_kwargs": {"data_key": "data"}, # this goes to __call__ + # "resource": "RES", + # ...: ..., + # } + # datum = { + # "datum_id": "b", + # "datum_kwargs": {"data_key": "omega"}, + # "resource": "RES", + # ...: ..., + # } + + # event = {...: ..., "data": {"detector_img": "a", "omega": "b"}} + + for data_key in self._datum_ids.keys(): + datum_id = f"{resource_uid}/{data_key}" + self._datum_ids[data_key] = datum_id + datum = { + "resource": resource_uid, + "datum_id": datum_id, + "datum_kwargs": {"data_key": data_key}, + } + asset_docs_cache.append(("datum", datum)) + return tuple(asset_docs_cache) + + def _extract_metadata(self, field="omega"): + with h5py.File(self._master_file, "r") as hf: + return hf.get(f"entry/sample/goniometer/{field}")[()] class MD2VectorFlyer(MD2StandardFlyer): def __init__(self, md2, detector=None) -> None: From c983dcc3e3df841bb9f4e28694a424acb5feb062 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 15:34:10 -0400 Subject: [PATCH 114/234] flyer fixes --- md2_flyers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 646411e6..74b86237 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -62,11 +62,10 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im file_prefix_minus_directory = file_prefix_minus_directory.split("/")[-1] self.detector.cam.acquire_time.put(exposure_per_image) self.detector.cam.acquire_period.put(exposure_per_image) + self.detector.cam.num_triggers.put(total_num_images) self.detector.cam.trigger_mode.put( EXTERNAL_ENABLE ) # must be external_enable to get the correct number of triggers and stop acquire - self.detector.cam.num_images.put(total_num_images) - self.detector.cam.num_triggers.put(total_num_images) self.detector.cam.file_path.put(data_directory_name) self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") self.detector.cam.sequence_id.put(file_number_start) From af557661c240249c90c2f725de4de11f4f7d4597 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 16:19:53 -0400 Subject: [PATCH 115/234] fixes flyer metadata issues --- daq_macros.py | 4 ++-- md2_flyers.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 326be2b4..9352e2cb 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3699,7 +3699,6 @@ def standardDaq(currentRequest): flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) - flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) @@ -3711,6 +3710,7 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return + flyer.detector.stage() start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) @@ -3766,7 +3766,6 @@ def vectorDaq(currentRequest): vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file) - flyer.detector.stage() def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) @@ -3778,6 +3777,7 @@ def armed_callback(value, old_value, **kwargs): if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") return + flyer.detector.stage() start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) diff --git a/md2_flyers.py b/md2_flyers.py index 74b86237..3e88276d 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -55,6 +55,8 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m, num_images_per_file): self.detector.cam.save_files.put(1) + self.detector.cam.sequence_id.put(file_number_start) + self.detector.cam.det_distance.put(det_distance_m) self.detector.cam.file_owner.put(getpass.getuser()) self.detector.cam.file_owner_grp.put(grp.getgrgid(os.getgid())[0]) self.detector.cam.file_perms.put(420) @@ -68,23 +70,21 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im ) # must be external_enable to get the correct number of triggers and stop acquire self.detector.cam.file_path.put(data_directory_name) self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") - self.detector.cam.sequence_id.put(file_number_start) self.detector.cam.beam_center_x.put(x_beam) self.detector.cam.beam_center_y.put(y_beam) self.detector.cam.omega_incr.put(img_width) self.detector.cam.omega_start.put(angle_start) self.detector.cam.wavelength.put(wavelength) - self.detector.cam.det_distance.put(det_distance_m) self.detector.file.file_write_images_per_file.put(num_images_per_file) - def armed_callback(value, old_value, **kwargs): - if old_value == 0 and value == 1: - return True - return False + #def armed_callback(value, old_value, **kwargs): + # if old_value == 0 and value == 1: + # return True + # return False - status = SubscriptionStatus(self.detector.cam.armed, armed_callback, run=False) - self.detector.cam.acquire.put(1) - yield status + #status = SubscriptionStatus(self.detector.cam.armed, armed_callback, run=False) + #self.detector.cam.acquire.put(1) + #yield status def complete(self): # monitor md2 status, wait for ready or timeout and then return @@ -205,4 +205,4 @@ def update_parameters(self, start_angle, scan_range, exposure_time, start_y, sta "start_z": start_z, "stop_y": stop_y, "stop_z": stop_z, - } \ No newline at end of file + } From dc60737e3ebcb3d59348c9c5f67aa3a4b3862d9c Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 31 Aug 2023 16:24:26 -0400 Subject: [PATCH 116/234] remove testing values --- devices.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devices.py b/devices.py index f9d78c37..680705b0 100644 --- a/devices.py +++ b/devices.py @@ -206,13 +206,13 @@ def vector_scan(self, if start_cy is None: start_cy = self.cy.val() if stop_y is None: - stop_y = self.y.val()+0.1 + stop_y = self.y.val() if stop_z is None: - stop_z = self.z.val()+0.1 + stop_z = self.z.val() if stop_cx is None: - stop_cx = self.cx.val()+0.1 + stop_cx = self.cx.val() if stop_cy is None: - stop_cy = self.cy.val()+0.1 + stop_cy = self.cy.val() # List of scan parameters values, comma separated. The axes start values define the beginning # of the exposure, that is when all the axes have a steady speed and when the shutter/detector From 01babc1528f04e52e8984a903d10e58dfb7ef869 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 14 Sep 2023 12:49:15 -0400 Subject: [PATCH 117/234] fix to collection exposure time --- daq_macros.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 9352e2cb..a2ebfeac 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3674,13 +3674,13 @@ def standardDaq(currentRequest): file_number_start = reqObj["file_number_start"] sweep_start_angle = reqObj["sweep_start"] sweep_end_angle = reqObj["sweep_end"] - exposure_time = reqObj["exposure_time"] file_prefix = str(reqObj["file_prefix"]) data_directory_name = str(reqObj["directory"]) file_number_start = reqObj["file_number_start"] img_width = reqObj["img_width"] exposure_per_image = reqObj["exposure_time"] total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + total_exposure_time = exposure_per_image * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle num_images_per_file = total_num_images @@ -3715,7 +3715,7 @@ def armed_callback(value, old_value, **kwargs): yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - flyer.update_parameters(total_num_images, angle_start, scan_range, exposure_time) + flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) yield from bp.fly([flyer]) def vectorDaq(currentRequest): @@ -3735,13 +3735,13 @@ def vectorDaq(currentRequest): file_number_start = reqObj["file_number_start"] sweep_start_angle = reqObj["sweep_start"] sweep_end_angle = reqObj["sweep_end"] - exposure_time = reqObj["exposure_time"] file_prefix = str(reqObj["file_prefix"]) data_directory_name = str(reqObj["directory"]) file_number_start = reqObj["file_number_start"] img_width = reqObj["img_width"] exposure_per_image = reqObj["exposure_time"] total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + total_exposure_time = reqObj["exposure_time"] * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle num_images_per_file = total_num_images @@ -3782,7 +3782,7 @@ def armed_callback(value, old_value, **kwargs): yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - vector_flyer.update_parameters(angle_start, scan_range, exposure_time, start_y, start_z, stop_y, stop_z) + vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z) yield from bp.fly([vector_flyer]) def clean_up_collection(currentRequest): From 7a283507d1666149bbb17ba9626a537ef9fe35c3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 14 Sep 2023 13:27:46 -0400 Subject: [PATCH 118/234] additional logging for lsdc --- daq_macros.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index a2ebfeac..1fa47764 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3716,7 +3716,9 @@ def armed_callback(value, old_value, **kwargs): md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) + logger.info(f"flyer handoff") yield from bp.fly([flyer]) + logger.info(f"fly complete") def vectorDaq(currentRequest): # collect all parameters From 441ef13c47e54860b136de45e2a3c14116e7e3b7 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 15 Sep 2023 11:32:49 -0400 Subject: [PATCH 119/234] added is_ready check to md2 --- devices.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/devices.py b/devices.py index 680705b0..f113d2ba 100644 --- a/devices.py +++ b/devices.py @@ -153,6 +153,9 @@ class MD2Device(GonioDevice): detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=int(os.environ['EXPORTER_PORT']), name='exporter') + def is_ready(self): + return self.state.get() == 4 + def ready_status(self): # returns an ophyd status object that monitors the state pv for operations to complete def check_ready(*, old_value, value, **kwargs): From 3e443438889fb71a0525e46a07ddc4b94188ad25 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 15 Sep 2023 11:38:53 -0400 Subject: [PATCH 120/234] extend timeout for longer collections --- md2_flyers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/md2_flyers.py b/md2_flyers.py index 3e88276d..ae4f038f 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -89,7 +89,8 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im def complete(self): # monitor md2 status, wait for ready or timeout and then return ready_status = self.md2.ready_status() - ready_status.wait(timeout=20) + timeout = self.collection_params["exposure_time"] + 10 + ready_status.wait(timeout=timeout) return ready_status def describe_collect(self): From 7bd1b3a3f18275f9566a236989a1ef3846c47b07 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 10:38:00 -0400 Subject: [PATCH 121/234] adding fine motors to md2positioners --- beamline_support.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beamline_support.py b/beamline_support.py index ce4e17c6..104fc25e 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -320,9 +320,10 @@ def read_db(): def init_motors(): global motor_channel_dict + md2_motors = ["omega","sampleX","sampleY","sampleZ", "finex", "finey", "finez"] for key in list(motor_dict.keys()): - if beamline_designation == "XF:19ID" and ((key == "omega") or (key == "sampleX") or (key == "sampleY") or (key == "sampleZ")): + if beamline_designation == "XF:19ID" and key in md2_motors: motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) else: motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) From 74c6b0b4a2738f069e7971f8b0f0f4d489a3a0a3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 12:32:38 -0400 Subject: [PATCH 122/234] vector testing in progress --- daq_macros.py | 2 ++ devices.py | 2 ++ gui/control_main.py | 1 + 3 files changed, 5 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index 1fa47764..e48b6db8 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3664,6 +3664,7 @@ def standardDaq(currentRequest): # perform governor and phase transitions # update flyer parameters # fly + logger.info(f"req object: {reqObj}") x_beam = getPvDesc("beamCenterX") y_beam = getPvDesc("beamCenterY") wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) @@ -3732,6 +3733,7 @@ def vectorDaq(currentRequest): wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 reqObj = currentRequest["request_obj"] + logger.info(f"req object: {reqObj}") file_prefix = str(reqObj["file_prefix"]) data_directory_name = str(reqObj["directory"]) file_number_start = reqObj["file_number_start"] diff --git a/devices.py b/devices.py index f113d2ba..d2da06d7 100644 --- a/devices.py +++ b/devices.py @@ -138,6 +138,8 @@ class GonioDevice(Device): x = Cpt(MD2Positioner, 'AlignmentX',name='x') y = Cpt(MD2Positioner, 'AlignmentY',name='y') z = Cpt(MD2Positioner, 'AlignmentZ',name='z') + cx = Cpt(MD2Positioner, 'CentringX',name='cx') + cy = Cpt(MD2Positioner, 'CentringY',name='cy') class MD2Device(GonioDevice): # TODO: Enum for MD2 phases and states diff --git a/gui/control_main.py b/gui/control_main.py index fa5f6ee2..e28db1a4 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3615,6 +3615,7 @@ def timerSampleRefresh(self): if self.capture is None: return retval, self.currentFrame = self.capture.read() + self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_AREA) if self.currentFrame is None: logger.debug( "no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct" From 531da8b95b21054d3b2e2de01b21570dbf24ceab Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 12:47:29 -0400 Subject: [PATCH 123/234] add cx cy to vector scan definitions --- daq_macros.py | 8 +++++--- md2_flyers.py | 10 +++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index e48b6db8..55158452 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3752,10 +3752,12 @@ def vectorDaq(currentRequest): wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) vector_params = reqObj["vectorParams"] - #x_vec_start=vector_params["vecStart"]["x"] start_y=vector_params["vecStart"]["y"] start_z=vector_params["vecStart"]["z"] - #x_vec_end=vector_params["vecEnd"]["x"] + start_cx=vector_params["vecStart"]["finex"] + start_cy=vector_params["vecStart"]["finey"] + stop_cx=vector_params["vecEnd"]["finex"] + stop_cy=vector_params["vecEnd"]["finey"] stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] @@ -3786,7 +3788,7 @@ def armed_callback(value, old_value, **kwargs): yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z) + vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) yield from bp.fly([vector_flyer]) def clean_up_collection(currentRequest): diff --git a/md2_flyers.py b/md2_flyers.py index ae4f038f..4dfb6be8 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -190,20 +190,28 @@ def kickoff(self): md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["scan_range"], exposure_time=self.collection_params["exposure_time"], + start_cx=self.collection_params["start_cx"], + start_cy=self.collection_params["start_cy"], start_y=self.collection_params["start_y"], start_z=self.collection_params["start_z"], + stop_cx=self.collection_params["stop_cx"], + stop_cy=self.collection_params["stop_cy"], stop_y=self.collection_params["stop_y"], stop_z=self.collection_params["stop_z"],) logger.info(f"md2_msg: {md2_msg}") return NullStatus() - def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z): + def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy): self.collection_params = { "start_angle": start_angle, "scan_range": scan_range, "exposure_time": exposure_time, + "start_cx": start_cx, + "start_cy": start_cy, "start_y": start_y, "start_z": start_z, + "stop_cx": stop_cx, + "stop_cy": stop_cy, "stop_y": stop_y, "stop_z": stop_z, } From 2c5b525c18b3b1055ff9024045ba4e504edaab24 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 14:55:05 -0400 Subject: [PATCH 124/234] raster first commit --- daq_macros.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++--- devices.py | 2 +- md2_flyers.py | 51 +++++++++++++++++++++++++++++ start_bs.py | 7 ++-- 4 files changed, 139 insertions(+), 9 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 55158452..ed124499 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1211,7 +1211,10 @@ def snakeRaster(rasterReqID,grain=""): if (scannerType == "PI"): snakeRasterFine(rasterReqID,grain) else: - finalize_plan = finalize_wrapper(snakeRasterBluesky(rasterReqID,grain), bps.mv(raster_flyer.detector.cam.acquire, 0)) + if daq_utils.beamline == "nyx": + yield from raster_plan_wrapped(rasterReqID) + else: + finalize_plan = finalize_wrapper(snakeRasterBluesky(rasterReqID,grain), bps.mv(raster_flyer.detector.cam.acquire, 0)) yield from finalize_plan #RE(snakeRasterBluesky(rasterReqID,grain)) @@ -3652,10 +3655,13 @@ def standard_zebra_plan(flyer,angle_start,num_images,scanWidth,imgWidth,exposure yield from final_plan def standard_plan_wrapped(currentRequest): - yield from finalize_wrapper(standardDaq(currentRequest), clean_up_collection(currentRequest)) + yield from finalize_wrapper(standardDaq(currentRequest), clean_up_collection()) def vector_plan_wrapped(currentRequest): - yield from finalize_wrapper(vectorDaq(currentRequest), clean_up_collection(currentRequest)) + yield from finalize_wrapper(vectorDaq(currentRequest), clean_up_collection()) + +def raster_plan_wrapped(rasterReqID): + yield from finalize_wrapper(rasterDaq(rasterReqID), clean_up_collection()) def standardDaq(currentRequest): # collect all parameters @@ -3791,7 +3797,81 @@ def armed_callback(value, old_value, **kwargs): vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) yield from bp.fly([vector_flyer]) -def clean_up_collection(currentRequest): +def rasterDaq(rasterReqID): + global rasterRowResultsList,processedRasterRowCount + data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, start_omega, start_x, start_y, start_z, omegaRad, number_of_lines, numsteps, totalImages, rows = params_from_raster_req_id(rasterReqID) + rasterRowResultsList = [{} for i in range(0,number_of_lines)] + processedRasterRowCount = 0 + rasterEncoderMap = {} + + xbeam = getPvDesc("beamCenterX") + ybeam = getPvDesc("beamCenterY") + + rasterRequest = db_lib.getRequestByID(rasterReqID) + reqObj = rasterRequest["request_obj"] + parentReqID = reqObj["parentReqID"] + parentReqProtocol = "" + if (parentReqID != -1): + parentRequest = db_lib.getRequestByID(parentReqID) + parentReqObj = parentRequest["request_obj"] + parentReqProtocol = parentReqObj["protocol"] + detDist = parentReqObj["detDist"] + + rasterFilePrefix = dataFilePrefix + "_Raster" + + logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") + logger.info(f"req_obj: {reqObj}") + + # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) + # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} + # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) + #omega_range + line_range = img_width_per_cell * num_cells + total_uturn_range = line_range * num_lines + #start_y + #start_z + #start_cx + #start_cy + #number_of_lines + #frames_per_lines + #exposure_time = exptimePerCell * totalImages + #invert_direction = True + #use_centring_table = True + #use_fast_mesh_scans = True + + #if raster_flyer.detector.cam.armed.get() == 1: + # daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' + # 'should check the most recent collection to determine if it was successful. Cancelling' + # 'this collection, retry when ready.') + # logger.warning("Detector was in the armed state prior to this attempted collection.") + # return 0 + #start_time = time.time() + #raster_flyer.configure_detector(rasterFilePrefix, data_directory_name) + #raster_flyer.detector_arm(start_omega, img_width, total_num_images, exposure_per_image, + # file_prefix, data_directory_name, file_number_start, x_beam, y_beam, + # wavelength, det_distance_m, num_images_per_file) + #def armed_callback(value, old_value, **kwargs): + # return (old_value == 0 and value == 1) + #arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) + #raster_flyer.detector.cam.acquire.put(1) + #govStatus = gov_lib.setGovRobot(gov_robot, "DA") + #arm_status.wait(timeout=10) + #govStatus.wait(timeout=20) + #logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") + #if govStatus.exception(): + # logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") + # return + #flyer.detector.stage() + #start_time = time.time() + #yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states + #md2.ready_status().wait(timeout=10) + #logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") + #raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_lines, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) + #yield from bp.fly([raster_flyer]) + + + +def clean_up_collection(): # this is a plan that should will always be run after a collection is complete start_time = time.time() yield from bps.mv(flyer.detector.cam.acquire, 0) diff --git a/devices.py b/devices.py index d2da06d7..45f742e8 100644 --- a/devices.py +++ b/devices.py @@ -244,7 +244,7 @@ def raster_scan(self, frames_per_line=5, # int: number of frames on the horizontal range. exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_table_centering=False, # boolean: true to use the centring table to do the pitch movements. + use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). ): diff --git a/md2_flyers.py b/md2_flyers.py index 4dfb6be8..5fcee44f 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -215,3 +215,54 @@ def update_parameters(self, start_angle, scan_range, exposure_time, start_y, sta "stop_y": stop_y, "stop_z": stop_z, } + +class MD2RasterFlyer(MD2StandardFlyer): + # List of scan parameters values, "/t" separated. The axes start values define the beginning + # of the exposure, that is when all the axes have a steady speed and when the shutter/detector + # are triggered. + # The axes stop values are for the end of detector exposure and define the position at the + # beginning of the deceleration. + # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", + # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", + # "invert_direction", "use_centring_table", "use_fast_mesh_scans" + + def __init__(self, md2, detector=None) -> None: + super().__init__(md2, detector) + self.name = "MD2RasterFlyer" + + def kickoff(self): + # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z + md2_msg = self.md2.raster_scan(omega_range=self.collection_params["omega_range"], + line_range=self.collection_params["line_range"], + total_uturn_range=self.collection_params["total_uturn_range"], + start_omega=self.collection_params["start_omega"], + start_y=self.collection_params["start_y"], + start_z=self.collection_params["start_z"], + start_cx=self.collection_params["start_cx"], + start_cy=self.collection_params["start_cy"], + number_of_lines=self.collection_params["number_of_lines"], + frames_per_lines=self.collection_params["frames_per_lines"], + exposure_time=self.collection_params["exposure_time"], + invert_direction=self.collection_params["invert_direction"], + use_centring_table=self.collection_params["use_centring_table"], + use_fast_mesh_scans=self.collection_params["use_fast_mesh_scans"]) + logger.info(f"md2_msg: {md2_msg}") + return NullStatus() + + def update_parameters(self, omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_lines, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans): + self.collection_params = { + "omega_range": omega_range, + "line_range": line_range, + "total_uturn_range": total_uturn_range, + "start_omega": start_omega, + "start_y": start_y, + "start_z": start_z, + "start_cx": start_cx, + "start_cy": start_cy, + "number_of_lines": number_of_lines, + "frames_per_lines": frames_per_lines, + "exposure_time": exposure_time, + "invert_direction": invert_direction, + "use_centring_table": use_centring_table, + "use_fast_mesh_scans": use_fast_mesh_scans, + } diff --git a/start_bs.py b/start_bs.py index 5b2d2680..e8f2258e 100755 --- a/start_bs.py +++ b/start_bs.py @@ -198,11 +198,10 @@ class SampleXYZ(Device): from mxtools.eiger import EigerSingleTriggerV26 detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) #from nyxtools.flyer_eiger2 import NYXEiger2Flyer - from md2_flyers import MD2StandardFlyer, MD2VectorFlyer + from md2_flyers import MD2StandardFlyer, MD2VectorFlyer, MD2RasterFlyer flyer = MD2StandardFlyer(md2, detector) vector_flyer = MD2VectorFlyer(md2, detector) - from nyxtools.nyx_raster_flyer import NYXRasterFlyer - raster_flyer = NYXRasterFlyer(vector, zebra, detector) + raster_flyer = MD2RasterFlyer(md2, detector) from nyxtools.isara_robot import IsaraRobotDevice from denso_robot import OphydRobot @@ -211,7 +210,7 @@ class SampleXYZ(Device): govs = _make_governors("XF:19IDC-ES", name="govs") gov_robot = govs.gov.Robot - + #back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") From f127d6399ce7ad64fb66a027049d08b34f66a7eb Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 15:07:00 -0400 Subject: [PATCH 125/234] attempting to fix buffering --- gui/control_main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index e28db1a4..5efabbb2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3615,7 +3615,8 @@ def timerSampleRefresh(self): if self.capture is None: return retval, self.currentFrame = self.capture.read() - self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_AREA) + self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_LINEAR) + self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) if self.currentFrame is None: logger.debug( "no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct" From ff554f10f157f74a4c9a10dd30c268e06ef2e20f Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 15:50:11 -0400 Subject: [PATCH 126/234] update FOV when camera changes --- gui/control_main.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index 5efabbb2..c477d517 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1701,6 +1701,11 @@ def flushBuffer(self, vidStream): def zoomLevelComboActivatedCB(self, identifier): self.camera.zoom.put(identifier) + daq_utils.lowMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() + daq_utils.lowMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() + daq_utils.highMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() + daq_utils.highMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() + def zoomLevelToggledCB(self, identifier): fov = {} @@ -4880,6 +4885,10 @@ def initOphyd(self): self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") + daq_utils.lowMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() + daq_utils.lowMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() + daq_utils.highMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() + daq_utils.highMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() else: pass From b7a83fe04ef04e075a0ce0b7d7560e93aeb1f41c Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sat, 16 Sep 2023 16:18:24 -0400 Subject: [PATCH 127/234] add proper scale calculations --- gui/control_main.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index c477d517..b77eb47e 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1701,11 +1701,6 @@ def flushBuffer(self, vidStream): def zoomLevelComboActivatedCB(self, identifier): self.camera.zoom.put(identifier) - daq_utils.lowMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() - daq_utils.lowMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() - daq_utils.highMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() - daq_utils.highMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() - def zoomLevelToggledCB(self, identifier): fov = {} @@ -3383,24 +3378,32 @@ def getCurrentFOV(self): return fov def screenXPixels2microns(self, pixels): - fov = self.getCurrentFOV() - fovX = fov["x"]*1000 - return float(pixels) * (fovX / daq_utils.screenPixX) + md2_img_width = self.md2.center_pixel_x.get() * 2.0 + lsdc_img_width = daq_utils.screenPixX + img_scale_factor = md2_img_width / lsdc_img_width + pixels_per_micron = self.camera.scale_x.get() * 1000 + return float(pixels * img_scale_factor) / pixels_per_micron def screenYPixels2microns(self, pixels): - fov = self.getCurrentFOV() - fovY = fov["y"]*1000 - return float(pixels) * (fovY / daq_utils.screenPixY) + md2_img_height = self.md2.center_pixel_y.get() * 2.0 + lsdc_img_height = daq_utils.screenPixY + pixels_per_micron = self.camera.scale_y.get() * 1000 + img_scale_factor = md2_img_height / lsdc_img_height + return float(pixels * img_scale_factor) / pixels_per_micron def screenXmicrons2pixels(self, microns): - fov = self.getCurrentFOV() - fovX = fov["x"]*1000 - return int(round(microns * (daq_utils.screenPixX / fovX))) + md2_img_width = self.md2.center_pixel_x.get() * 2.0 + lsdc_img_width = daq_utils.screenPixX + pixels_per_micron = self.camera.scale_x.get() * 1000 + img_scale_factor = md2_img_width / lsdc_img_width + return float(microns * pixels_per_micron) / img_scale_factor def screenYmicrons2pixels(self, microns): - fov = self.getCurrentFOV() - fovY = fov["y"]*1000 - return int(round(microns * (daq_utils.screenPixY / fovY))) + md2_img_height = self.md2.center_pixel_y.get() * 2.0 + lsdc_img_height = daq_utils.screenPixY + pixels_per_micron = self.camera.scale_y.get() * 1000 + img_scale_factor = md2_img_height / lsdc_img_height + return float(microns * pixels_per_micron) / img_scale_factor def definePolyRaster( self, raster_w, raster_h, stepsizeXPix, stepsizeYPix, point_x, point_y, stepsize @@ -4885,10 +4888,6 @@ def initOphyd(self): self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") - daq_utils.lowMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() - daq_utils.lowMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() - daq_utils.highMagFOVx = (self.md2.center_pixel_x.get() * 2) / self.camera.scale_x.get() - daq_utils.highMagFOVy = (self.md2.center_pixel_y.get() * 2) / self.camera.scale_y.get() else: pass From a974b638aadeebf07d1f47430ddf68e6957a910e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 18 Sep 2023 15:37:28 -0400 Subject: [PATCH 128/234] frame data testing --- gui/control_main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index b77eb47e..29703ff7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3622,8 +3622,11 @@ def drawPolyRaster( def timerSampleRefresh(self): if self.capture is None: return + start_time = time.time() retval, self.currentFrame = self.capture.read() - self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_LINEAR) + capture_time = time.time() + # uncomment this for frame resizing + # self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_LINEAR) self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) if self.currentFrame is None: logger.debug( @@ -3635,8 +3638,11 @@ def timerSampleRefresh(self): self.currentFrame, width, height, 3 * width, QtGui.QImage.Format_RGB888 ) qimage = qimage.rgbSwapped() + swap_time = time.time() pixmap_orig = QtGui.QPixmap.fromImage(qimage) self.pixmap_item.setPixmap(pixmap_orig) + end_time = time.time() + logger.info(f"capture time: {capture_time - start_time}, swap time: {end_time - swap_time}, total time: {end_time - start_time}") def sceneKey(self, event): if ( From 104ee1f87aa4b0458dcb9120334e9214b2dd7600 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 13:13:52 -0400 Subject: [PATCH 129/234] testing for video timer --- daq_macros.py | 2 -- gui/control_main.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index ed124499..b573739d 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3670,7 +3670,6 @@ def standardDaq(currentRequest): # perform governor and phase transitions # update flyer parameters # fly - logger.info(f"req object: {reqObj}") x_beam = getPvDesc("beamCenterX") y_beam = getPvDesc("beamCenterY") wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) @@ -3739,7 +3738,6 @@ def vectorDaq(currentRequest): wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 reqObj = currentRequest["request_obj"] - logger.info(f"req object: {reqObj}") file_prefix = str(reqObj["file_prefix"]) data_directory_name = str(reqObj["directory"]) file_number_start = reqObj["file_number_start"] diff --git a/gui/control_main.py b/gui/control_main.py index 29703ff7..98980e57 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3626,8 +3626,8 @@ def timerSampleRefresh(self): retval, self.currentFrame = self.capture.read() capture_time = time.time() # uncomment this for frame resizing - # self.currentFrame = cv2.resize(self.currentFrame, (640,512), interpolation=cv2.INTER_LINEAR) - self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) + # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) + # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 512) if self.currentFrame is None: logger.debug( "no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct" From b4459b61b7e74d22698d953153f2668babd75d13 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 13:54:11 -0400 Subject: [PATCH 130/234] sample camera threading --- gui/control_main.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 98980e57..4b830cc8 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3619,15 +3619,23 @@ def drawPolyRaster( } self.rasterList.append(newRasterGraphicsDesc) + def sampleCameraThread(self): + while self.cameraThreadActive: + if self.capture is None: + return + self.timerSampleRefresh() + time.sleep(SAMPLE_TIMER_DELAY) + return + def timerSampleRefresh(self): if self.capture is None: return - start_time = time.time() - retval, self.currentFrame = self.capture.read() - capture_time = time.time() # uncomment this for frame resizing # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 512) + start_time = time.time() + retval, self.currentFrame = self.capture.read() + capture_time = time.time() if self.currentFrame is None: logger.debug( "no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct" @@ -3638,11 +3646,10 @@ def timerSampleRefresh(self): self.currentFrame, width, height, 3 * width, QtGui.QImage.Format_RGB888 ) qimage = qimage.rgbSwapped() - swap_time = time.time() pixmap_orig = QtGui.QPixmap.fromImage(qimage) self.pixmap_item.setPixmap(pixmap_orig) end_time = time.time() - logger.info(f"capture time: {capture_time - start_time}, swap time: {end_time - swap_time}, total time: {end_time - start_time}") + logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") def sceneKey(self, event): if ( From dc8fbbcaedbef41ba5019ca886d76607fab03b7f Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:01:18 -0400 Subject: [PATCH 131/234] starting threading --- gui/control_main.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 4b830cc8..9d919de7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -7,6 +7,7 @@ import sys import time from typing import Dict, List, Optional +import threading import cv2 import numpy as np @@ -1052,9 +1053,11 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag - self.timerSample = QTimer() - self.timerSample.timeout.connect(self.timerSampleRefresh) - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample = QTimer() + #self.timerSample.timeout.connect(self.timerSampleRefresh) + #self.timerSample.start(SAMPLE_TIMER_DELAY) + self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadCB) + self.sampleCameraThread.start() self.centeringMarksList = [] self.rasterList = [] @@ -3619,7 +3622,8 @@ def drawPolyRaster( } self.rasterList.append(newRasterGraphicsDesc) - def sampleCameraThread(self): + def sampleCameraThreadLoop(self): + self.cameraThreadActive = True while self.cameraThreadActive: if self.capture is None: return From b6674ac8c832c646304affc90446fccff8dfab9f Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:04:29 -0400 Subject: [PATCH 132/234] loop name --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 9d919de7..842ccd03 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1056,7 +1056,7 @@ def createSampleTab(self): #self.timerSample = QTimer() #self.timerSample.timeout.connect(self.timerSampleRefresh) #self.timerSample.start(SAMPLE_TIMER_DELAY) - self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadCB) + self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadLoop) self.sampleCameraThread.start() self.centeringMarksList = [] From 6f9e09879aa2d1f7f54dae7cd9ddc317d92fa7f0 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:07:54 -0400 Subject: [PATCH 133/234] 50s delay hmmm --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 842ccd03..8f0f82c7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3628,7 +3628,7 @@ def sampleCameraThreadLoop(self): if self.capture is None: return self.timerSampleRefresh() - time.sleep(SAMPLE_TIMER_DELAY) + time.sleep(SAMPLE_TIMER_DELAY/1000) return def timerSampleRefresh(self): From 22b941e1823a2a6d27a3013fd8452bf582276d82 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:16:46 -0400 Subject: [PATCH 134/234] event wait instead? --- gui/control_main.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 8f0f82c7..71072da7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1053,9 +1053,10 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag - #self.timerSample = QTimer() - #self.timerSample.timeout.connect(self.timerSampleRefresh) - #self.timerSample.start(SAMPLE_TIMER_DELAY) + self.timerSample = QTimer() + self.sampleFrameEvent = threading.Event() + self.timerSample.timeout.connect(self.sampleFrameEvent.set) + self.timerSample.start(SAMPLE_TIMER_DELAY) self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadLoop) self.sampleCameraThread.start() @@ -3625,10 +3626,10 @@ def drawPolyRaster( def sampleCameraThreadLoop(self): self.cameraThreadActive = True while self.cameraThreadActive: + self.sampleFrameEvent.wait() if self.capture is None: return self.timerSampleRefresh() - time.sleep(SAMPLE_TIMER_DELAY/1000) return def timerSampleRefresh(self): From a599ee1e534cf79ee1cae8a5e14cf0c9423262e3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:33:55 -0400 Subject: [PATCH 135/234] testing signal solution --- gui/control_main.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 71072da7..5da7af21 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -18,7 +18,7 @@ from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel from qtpy import QtCore, QtGui, QtWidgets -from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer +from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer, pyqtSignal, pyqtSlot from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication from devices import GonioDevice, CameraDevice, MD2Device, LightDevice @@ -1058,6 +1058,7 @@ def createSampleTab(self): self.timerSample.timeout.connect(self.sampleFrameEvent.set) self.timerSample.start(SAMPLE_TIMER_DELAY) self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadLoop) + self.sampleCameraThread.frame_ready.connect(self.updateSampleImage) self.sampleCameraThread.start() self.centeringMarksList = [] @@ -3624,6 +3625,7 @@ def drawPolyRaster( self.rasterList.append(newRasterGraphicsDesc) def sampleCameraThreadLoop(self): + self.frame_ready = pyqtSignal(np.ndarray) self.cameraThreadActive = True while self.cameraThreadActive: self.sampleFrameEvent.wait() @@ -3652,10 +3654,15 @@ def timerSampleRefresh(self): ) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) - self.pixmap_item.setPixmap(pixmap_orig) + #self.pixmap_item.setPixmap(pixmap_orig) + self.frame_ready.emit(pixmap_orig) end_time = time.time() logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") + @pyqtSlot(np.ndarray) + def updateSampleImage(self, frame): + self.pixmap_item.setPixmap(frame) + def sceneKey(self, event): if ( event.key() == QtCore.Qt.Key_Delete From f154cd1f3d9cbd77dcc0e82fd9c5fb1520a7d073 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:35:32 -0400 Subject: [PATCH 136/234] fix imports --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 5da7af21..635512bc 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -17,8 +17,8 @@ from PyMca5.PyMcaPhysics.xrf import Elements from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel -from qtpy import QtCore, QtGui, QtWidgets -from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer, pyqtSignal, pyqtSlot +from qtpy import QtCore, QtGui, QtWidgets, pyqtSignal, pyqtSlot +from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication from devices import GonioDevice, CameraDevice, MD2Device, LightDevice From a42978d7e76a447b9dc77e83bc7cd021dfa6b15a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:38:20 -0400 Subject: [PATCH 137/234] update to qtpy usages --- gui/control_main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 635512bc..45a4b768 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -17,8 +17,8 @@ from PyMca5.PyMcaPhysics.xrf import Elements from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel -from qtpy import QtCore, QtGui, QtWidgets, pyqtSignal, pyqtSlot -from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer +from qtpy import QtCore, QtGui, QtWidgets +from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer, Signal, Slot from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication from devices import GonioDevice, CameraDevice, MD2Device, LightDevice @@ -3625,7 +3625,6 @@ def drawPolyRaster( self.rasterList.append(newRasterGraphicsDesc) def sampleCameraThreadLoop(self): - self.frame_ready = pyqtSignal(np.ndarray) self.cameraThreadActive = True while self.cameraThreadActive: self.sampleFrameEvent.wait() @@ -3635,6 +3634,7 @@ def sampleCameraThreadLoop(self): return def timerSampleRefresh(self): + self.frame_ready = Signal(np.ndarray) if self.capture is None: return # uncomment this for frame resizing @@ -3659,7 +3659,7 @@ def timerSampleRefresh(self): end_time = time.time() logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") - @pyqtSlot(np.ndarray) + @Slot(np.ndarray) def updateSampleImage(self, frame): self.pixmap_item.setPixmap(frame) From 0e725288b13c756f66b3d990a0506f8a9ebb0546 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 14:41:48 -0400 Subject: [PATCH 138/234] redefine frame ready signal --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 45a4b768..636cdc2c 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1053,12 +1053,13 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag + self.frame_ready = Signal(np.ndarray) + self.frame_ready.connect(self.updateSampleImage) self.timerSample = QTimer() self.sampleFrameEvent = threading.Event() self.timerSample.timeout.connect(self.sampleFrameEvent.set) self.timerSample.start(SAMPLE_TIMER_DELAY) self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadLoop) - self.sampleCameraThread.frame_ready.connect(self.updateSampleImage) self.sampleCameraThread.start() self.centeringMarksList = [] @@ -3634,7 +3635,6 @@ def sampleCameraThreadLoop(self): return def timerSampleRefresh(self): - self.frame_ready = Signal(np.ndarray) if self.capture is None: return # uncomment this for frame resizing From 045f2cf255dfd1495688d72a8fe8bfcc8edeef05 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 15:07:29 -0400 Subject: [PATCH 139/234] Thread controls --- gui/control_main.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 636cdc2c..382d52bb 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -9,6 +9,7 @@ from typing import Dict, List, Optional import threading +from queue import Queue import cv2 import numpy as np from epics import PV @@ -18,7 +19,7 @@ from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel from qtpy import QtCore, QtGui, QtWidgets -from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer, Signal, Slot +from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication from devices import GonioDevice, CameraDevice, MD2Device, LightDevice @@ -1053,14 +1054,11 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag - self.frame_ready = Signal(np.ndarray) - self.frame_ready.connect(self.updateSampleImage) + self.frame_queue = Queue() + self.active_camera_threads = [] self.timerSample = QTimer() - self.sampleFrameEvent = threading.Event() - self.timerSample.timeout.connect(self.sampleFrameEvent.set) + self.timerSample.timeout.connect(self.sampleFrameCB) self.timerSample.start(SAMPLE_TIMER_DELAY) - self.sampleCameraThread = threading.Thread(target=self.sampleCameraThreadLoop) - self.sampleCameraThread.start() self.centeringMarksList = [] self.rasterList = [] @@ -3625,14 +3623,16 @@ def drawPolyRaster( } self.rasterList.append(newRasterGraphicsDesc) - def sampleCameraThreadLoop(self): - self.cameraThreadActive = True - while self.cameraThreadActive: - self.sampleFrameEvent.wait() - if self.capture is None: - return - self.timerSampleRefresh() - return + def sampleFrameCB(self): + for thread in self.active_camera_threads: + if not thread.is_alive(): # remove old threads + self.active_camera_threads.remove(thread) + if not len(self.active_camera_threads) > 0: + self.active_camera_threads.append(threading.Thread(target=self.timerSampleRefresh)) + self.active_camera_threads[-1].start() + + if not self.frame_queue.empty(): + self.pixmap_item.setPixmap(self.frame_queue.get()) def timerSampleRefresh(self): if self.capture is None: @@ -3654,15 +3654,10 @@ def timerSampleRefresh(self): ) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) - #self.pixmap_item.setPixmap(pixmap_orig) - self.frame_ready.emit(pixmap_orig) + self.frame_queue.put(pixmap_orig) end_time = time.time() logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") - @Slot(np.ndarray) - def updateSampleImage(self, frame): - self.pixmap_item.setPixmap(frame) - def sceneKey(self, event): if ( event.key() == QtCore.Qt.Key_Delete From 6cbc0b6b426edc491c9295ddbe9c4310e3fb56aa Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 15:45:06 -0400 Subject: [PATCH 140/234] shuffling frame queue --- gui/control_main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 382d52bb..26032a25 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3654,7 +3654,9 @@ def timerSampleRefresh(self): ) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) - self.frame_queue.put(pixmap_orig) + while(self.frame_queue.qsize() > 1): + self.frame_queue.get() # remove old frames + self.frame_queue.put(pixmap_orig) end_time = time.time() logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") From c2ab7618802a7c60739d83e39056fa05e0f1ec13 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 15:48:33 -0400 Subject: [PATCH 141/234] fixed indentation giving it different meaning --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 26032a25..68faca8b 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3656,7 +3656,7 @@ def timerSampleRefresh(self): pixmap_orig = QtGui.QPixmap.fromImage(qimage) while(self.frame_queue.qsize() > 1): self.frame_queue.get() # remove old frames - self.frame_queue.put(pixmap_orig) + self.frame_queue.put(pixmap_orig) end_time = time.time() logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") From 1313e023f69167abcbb8feda7f5f2d8d0d33ea81 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 16:17:53 -0400 Subject: [PATCH 142/234] add aperture device --- devices.py | 14 +++++++++++++- gui/control_main.py | 15 +++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/devices.py b/devices.py index 45f742e8..737ba3c3 100644 --- a/devices.py +++ b/devices.py @@ -92,7 +92,7 @@ def process_ret(self, ret): elif "NULL" in line: print(f"null error: {line}") return line - elif "EVT:" in line: + elif "EVT:" in line: # print here if you want to see the events pass #return self.process_evt(line) class LightDevice(Device): @@ -271,6 +271,18 @@ def raster_scan(self, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_table_centering, use_fast_mesh_scans] return self.exporter.cmd(command, param_list) + +class MD2ApertureDevice(MD2SimpleHVDevice): + # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" + current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_diameter') + diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') + + def get_diameter_list(self): + return self.diameters.get() + + def set_diameter(self, diameter): + if diameter in self.get_diameter_list(): + self.current_index.put(self.get_diameter_list().index(diameter)) class ShutterDevice(Device): control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal diff --git a/gui/control_main.py b/gui/control_main.py index 68faca8b..65b70529 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -22,7 +22,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice, CameraDevice, MD2Device, LightDevice +from devices import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice import daq_utils import db_lib @@ -563,8 +563,12 @@ def createSampleTab(self): setTransButton = QtWidgets.QPushButton("Set Trans") setTransButton.clicked.connect(self.setTransCB) beamsizeLabel = QtWidgets.QLabel("BeamSize:") + if daq_utils.beamline == "nyx": + beamSizeOptionList = self.aperture.get_diameter_list() + else: + beamSizeOptionList = BEAMSIZE_OPTIONS.keys() self.beamsizeComboBox = QtWidgets.QComboBox(self) - self.beamsizeComboBox.addItems(BEAMSIZE_OPTIONS.keys()) + self.beamsizeComboBox.addItems(beamSizeOptionList) self.beamsizeComboBox.setCurrentIndex(int(self.beamSize_pv.get())) self.beamsizeComboBox.activated[str].connect(self.beamsizeComboActivatedCB) if daq_utils.beamline == "amx" or self.energy_pv.get() < 9000: @@ -2599,8 +2603,10 @@ def protoRadioToggledCB(self, text): pass def beamsizeComboActivatedCB(self, text): - self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) - + if daq_utils.beamline == "nyx": + self.aperture.set_diameter(text) + else: + self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) def protoComboActivatedCB(self, text): self.showProtParams() @@ -4910,6 +4916,7 @@ def initOphyd(self): self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") + self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") else: pass From b7b9ca057de1ba163540e40ce9173363eee18f1a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 17:17:59 -0400 Subject: [PATCH 143/234] cap buffer size to 1 --- gui/control_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/control_main.py b/gui/control_main.py index 65b70529..a0bf0eb6 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3648,6 +3648,7 @@ def timerSampleRefresh(self): # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 512) start_time = time.time() retval, self.currentFrame = self.capture.read() + self.currentFrame.set(cv2.CAP_PROP_BUFFER_SIZE, 1) capture_time = time.time() if self.currentFrame is None: logger.debug( From 0385d1a2e08f47cff4a962c77c0b721673207ce8 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 17:19:37 -0400 Subject: [PATCH 144/234] cap buffer size --- gui/control_main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index a0bf0eb6..9ebef1e4 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1058,6 +1058,7 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.capture = self.captureLowMag + self.capture.set(cv2.CAP_PROP_BUFFER_SIZE, 1) self.frame_queue = Queue() self.active_camera_threads = [] self.timerSample = QTimer() @@ -3648,7 +3649,7 @@ def timerSampleRefresh(self): # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 512) start_time = time.time() retval, self.currentFrame = self.capture.read() - self.currentFrame.set(cv2.CAP_PROP_BUFFER_SIZE, 1) + self.capture.set(cv2.CAP_PROP_BUFFER_SIZE, 1) capture_time = time.time() if self.currentFrame is None: logger.debug( From f3b14a2230a15973b37c9962fe14d1f176322ff0 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 19 Sep 2023 17:24:02 -0400 Subject: [PATCH 145/234] Revert "add aperture device" This reverts commit 382a47be320df7a735af9c1dee9fabb5e1933f50. --- devices.py | 14 +------------- gui/control_main.py | 13 +++---------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/devices.py b/devices.py index 737ba3c3..45f742e8 100644 --- a/devices.py +++ b/devices.py @@ -92,7 +92,7 @@ def process_ret(self, ret): elif "NULL" in line: print(f"null error: {line}") return line - elif "EVT:" in line: # print here if you want to see the events + elif "EVT:" in line: pass #return self.process_evt(line) class LightDevice(Device): @@ -271,18 +271,6 @@ def raster_scan(self, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_table_centering, use_fast_mesh_scans] return self.exporter.cmd(command, param_list) - -class MD2ApertureDevice(MD2SimpleHVDevice): - # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" - current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_diameter') - diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') - - def get_diameter_list(self): - return self.diameters.get() - - def set_diameter(self, diameter): - if diameter in self.get_diameter_list(): - self.current_index.put(self.get_diameter_list().index(diameter)) class ShutterDevice(Device): control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal diff --git a/gui/control_main.py b/gui/control_main.py index 9ebef1e4..691de466 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -22,7 +22,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice +from devices import GonioDevice, CameraDevice, MD2Device, LightDevice import daq_utils import db_lib @@ -563,10 +563,7 @@ def createSampleTab(self): setTransButton = QtWidgets.QPushButton("Set Trans") setTransButton.clicked.connect(self.setTransCB) beamsizeLabel = QtWidgets.QLabel("BeamSize:") - if daq_utils.beamline == "nyx": - beamSizeOptionList = self.aperture.get_diameter_list() - else: - beamSizeOptionList = BEAMSIZE_OPTIONS.keys() + beamSizeOptionList = BEAMSIZE_OPTIONS.keys() self.beamsizeComboBox = QtWidgets.QComboBox(self) self.beamsizeComboBox.addItems(beamSizeOptionList) self.beamsizeComboBox.setCurrentIndex(int(self.beamSize_pv.get())) @@ -2604,10 +2601,7 @@ def protoRadioToggledCB(self, text): pass def beamsizeComboActivatedCB(self, text): - if daq_utils.beamline == "nyx": - self.aperture.set_diameter(text) - else: - self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) + self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) def protoComboActivatedCB(self, text): self.showProtParams() @@ -4918,7 +4912,6 @@ def initOphyd(self): self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") - self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") else: pass From 1623fce8659b8d26d8dfac7ccf7a3ffdf3b321d1 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 20 Sep 2023 12:28:10 -0400 Subject: [PATCH 146/234] trying with QThread --- gui/control_main.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 691de466..b52ac88f 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1053,14 +1053,14 @@ def createSampleTab(self): self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') - self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) - self.capture = self.captureLowMag - self.capture.set(cv2.CAP_PROP_BUFFER_SIZE, 1) - self.frame_queue = Queue() - self.active_camera_threads = [] - self.timerSample = QTimer() - self.timerSample.timeout.connect(self.sampleFrameCB) - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + #self.capture = self.captureLowMag + #self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1) + #self.frame_queue = Queue() + #self.active_camera_threads = [] + #self.timerSample = QTimer() + #self.timerSample.timeout.connect(self.sampleFrameCB) + #self.timerSample.start(SAMPLE_TIMER_DELAY) self.centeringMarksList = [] self.rasterList = [] @@ -1546,6 +1546,15 @@ def createSampleTab(self): self.vidActionRasterDefRadio.setEnabled(True) self.vidActionDefineCenterRadio.setDisabled(True) + + self.sampleCameraThread = VideoThread( + parent=self, delay=SAMPLE_TIMER_DELAY, url=getBlConfig("lowMagCamURL") + ) + self.sampleCameraThread.frame_ready.connect( + lambda frame: self.updateCam(self.pixmap_item, frame) + ) + self.sampleCameraThread.start() + self.hutchCornerCamThread = VideoThread( parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchCornerCamURL") ) @@ -3643,7 +3652,7 @@ def timerSampleRefresh(self): # self.currentFrame = self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 512) start_time = time.time() retval, self.currentFrame = self.capture.read() - self.capture.set(cv2.CAP_PROP_BUFFER_SIZE, 1) + self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1) capture_time = time.time() if self.currentFrame is None: logger.debug( From 275c76974e4cd4f8d9b455afb01451eddca55ae4 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 20 Sep 2023 12:32:16 -0400 Subject: [PATCH 147/234] use camera object --- gui/control_main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index b52ac88f..ec1953d0 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1546,9 +1546,11 @@ def createSampleTab(self): self.vidActionRasterDefRadio.setEnabled(True) self.vidActionDefineCenterRadio.setDisabled(True) - + + self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + self.capture = self.captureLowMag self.sampleCameraThread = VideoThread( - parent=self, delay=SAMPLE_TIMER_DELAY, url=getBlConfig("lowMagCamURL") + parent=self, delay=SAMPLE_TIMER_DELAY, camera_object=self.capture ) self.sampleCameraThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item, frame) From 4b8370e98f5337e40cea8f6befb22657bc0b22a8 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 20 Sep 2023 15:47:38 -0400 Subject: [PATCH 148/234] prevent buffering within videothread --- threads.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/threads.py b/threads.py index 45118e26..d92e3330 100644 --- a/threads.py +++ b/threads.py @@ -1,6 +1,7 @@ from qtpy.QtCore import QThread, QTimer, QEventLoop, Signal, QPoint, Qt, QObject from qtpy import QtGui from PIL import Image, ImageQt +import cv2 import os import sys import urllib @@ -34,6 +35,8 @@ def camera_refresh(self): self.showing_error = True if self.camera_object: + # https://stackoverflow.com/questions/30032063/opencv-videocapture-lag-due-to-the-capture-buffer + self.camera_object.set(cv2.CAP_PROP_POS_FRAMES, 0) retval,self.currentFrame = self.camera_object.read() if self.currentFrame is None: logger.debug('no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct') From 8af47a19b237a66b550dc0a3dde2aa63babe6ccf Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 25 Sep 2023 10:43:44 -0400 Subject: [PATCH 149/234] set images per file to 500, by request --- daq_macros.py | 6 ++---- md2_flyers.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index b573739d..cc9dc89a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3689,7 +3689,6 @@ def standardDaq(currentRequest): total_exposure_time = exposure_per_image * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle - num_images_per_file = total_num_images wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) if flyer.detector.cam.armed.get() == 1: @@ -3704,7 +3703,7 @@ def standardDaq(currentRequest): logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}, num_images_per_file {num_images_per_file}") flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m, num_images_per_file) + wavelength, det_distance_m) def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) @@ -3752,7 +3751,6 @@ def vectorDaq(currentRequest): total_exposure_time = reqObj["exposure_time"] * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle - num_images_per_file = total_num_images wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) vector_params = reqObj["vectorParams"] @@ -3775,7 +3773,7 @@ def vectorDaq(currentRequest): vector_flyer.configure_detector(file_prefix, data_directory_name) vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m, num_images_per_file) + wavelength, det_distance_m) def armed_callback(value, old_value, **kwargs): return (old_value == 0 and value == 1) arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) diff --git a/md2_flyers.py b/md2_flyers.py index 5fcee44f..75de23bc 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -53,7 +53,7 @@ def configure_detector(self, file_prefix, data_directory_name): def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m, num_images_per_file): + wavelength, det_distance_m): self.detector.cam.save_files.put(1) self.detector.cam.sequence_id.put(file_number_start) self.detector.cam.det_distance.put(det_distance_m) @@ -75,7 +75,7 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im self.detector.cam.omega_incr.put(img_width) self.detector.cam.omega_start.put(angle_start) self.detector.cam.wavelength.put(wavelength) - self.detector.file.file_write_images_per_file.put(num_images_per_file) + self.detector.file.file_write_images_per_file.put(500) #def armed_callback(value, old_value, **kwargs): # if old_value == 0 and value == 1: From 805d64f1bba5bda702503bbe3f1124aa3c4e35d4 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 27 Sep 2023 10:31:37 -0400 Subject: [PATCH 150/234] additional changes for video stability --- config_params.py | 2 +- daq_utils.py | 1 + gui/control_main.py | 66 ++++++++++++++++++++++++++++----------------- threads.py | 29 ++++++++++++++++---- 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/config_params.py b/config_params.py index 423a7117..95150c41 100644 --- a/config_params.py +++ b/config_params.py @@ -64,7 +64,7 @@ class OnMountAvailOptions(Enum): AUTO_RASTER = 2 # Mounts, centers and takes 2 orthogonal rasters HUTCH_TIMER_DELAY = 500 -SAMPLE_TIMER_DELAY = 50 +SAMPLE_TIMER_DELAY = 100 SERVER_CHECK_DELAY = 2000 FAST_DP_MIN_NODES = 4 diff --git a/daq_utils.py b/daq_utils.py index 8733a17c..8c4d79b6 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -54,6 +54,7 @@ def init_environment(): global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle,exporter_enabled + setBlConfig("highMagCamURL", "http://10.67.147.26:3908/video_feed2") owner = getpass.getuser() primaryDewarName = getBlConfig("primaryDewarName") db_lib.setPrimaryDewarName(primaryDewarName) diff --git a/gui/control_main.py b/gui/control_main.py index ec1953d0..f0390a2a 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -299,15 +299,15 @@ def closeEvent(self, evnt): sys.exit() # doing this to close any windows left open def initVideo2(self, frequency): - self.captureHighMag = cv2.VideoCapture(daq_utils.highMagCamURL) + #self.captureHighMag = cv2.VideoCapture(daq_utils.highMagCamURL) logger.debug('highMagCamURL: "' + daq_utils.highMagCamURL + '"') def initVideo4(self, frequency): - self.captureHighMagZoom = cv2.VideoCapture(daq_utils.highMagZoomCamURL) + #self.captureHighMagZoom = cv2.VideoCapture(daq_utils.highMagZoomCamURL) logger.debug('highMagZoomCamURL: "' + daq_utils.highMagZoomCamURL + '"') def initVideo3(self, frequency): - self.captureLowMagZoom = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) + #self.captureLowMagZoom = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) logger.debug('lowMagZoomCamURL: "' + daq_utils.lowMagZoomCamURL + '"') def createSampleTab(self): @@ -1050,16 +1050,16 @@ def createSampleTab(self): self.initVideo3, (0.25,) ) # this sets up lowMagDigiZoom if self.zoom1FrameRatePV.get() != 0: - self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) - #self.capture = self.captureLowMag - #self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1) + self.capture = self.captureLowMag #self.frame_queue = Queue() #self.active_camera_threads = [] - #self.timerSample = QTimer() + self.timerSample = QTimer() #self.timerSample.timeout.connect(self.sampleFrameCB) + #self.timerSample.timeout.connect(self.timerSampleRefresh) #self.timerSample.start(SAMPLE_TIMER_DELAY) self.centeringMarksList = [] @@ -1547,15 +1547,23 @@ def createSampleTab(self): self.vidActionDefineCenterRadio.setDisabled(True) - self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + #self.captureLowMag.set(cv2.CAP_PROP_BUFFERSIZE, 1) + self.captureLowMag = daq_utils.lowMagCamURL self.capture = self.captureLowMag + + #self.sampleCameraThread = VideoThread( + # parent=self, delay=SAMPLE_TIMER_DELAY, camera_object=self.capture + #) self.sampleCameraThread = VideoThread( - parent=self, delay=SAMPLE_TIMER_DELAY, camera_object=self.capture + parent=self, delay=HUTCH_TIMER_DELAY, url=daq_utils.highMagCamURL ) self.sampleCameraThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item, frame) ) self.sampleCameraThread.start() + + self.hutchCornerCamThread = VideoThread( parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchCornerCamURL") @@ -1718,6 +1726,9 @@ def flushBuffer(self, vidStream): def zoomLevelComboActivatedCB(self, identifier): self.camera.zoom.put(identifier) + #self.flushBuffer(self.capture) + #self.capture.release() + #self.capture = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) def zoomLevelToggledCB(self, identifier): fov = {} @@ -2707,7 +2718,7 @@ def popBaseDirectoryDialogCB(self): self.dataPathGB.setBasePath_ledit(fname) def popImportDialogCB(self): - self.timerSample.stop() + #self.timerSample.stop() fname = QtWidgets.QFileDialog.getOpenFileName( self, "Choose Spreadsheet File", @@ -2715,7 +2726,7 @@ def popImportDialogCB(self): filter="*.xls *.xlsx", options=QtWidgets.QFileDialog.DontUseNativeDialog, ) - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample.start(SAMPLE_TIMER_DELAY) if fname != "": self.send_to_server("importSpreadsheet", [fname[0], daq_utils.owner]) @@ -3636,6 +3647,12 @@ def drawPolyRaster( self.rasterList.append(newRasterGraphicsDesc) def sampleFrameCB(self): + frames = str(self.capture.get(cv2.CAP_PROP_BUFFERSIZE)) + text = frames + " frames" + self.imageScaleText = self.scene.addSimpleText( + text, font=QtGui.QFont("Times", 13) + ) + ''' for thread in self.active_camera_threads: if not thread.is_alive(): # remove old threads self.active_camera_threads.remove(thread) @@ -3645,7 +3662,7 @@ def sampleFrameCB(self): if not self.frame_queue.empty(): self.pixmap_item.setPixmap(self.frame_queue.get()) - + ''' def timerSampleRefresh(self): if self.capture is None: return @@ -3667,11 +3684,12 @@ def timerSampleRefresh(self): ) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) - while(self.frame_queue.qsize() > 1): - self.frame_queue.get() # remove old frames - self.frame_queue.put(pixmap_orig) + self.pixmap_item.setPixmap(pixmap_orig) + #while(self.frame_queue.qsize() > 1): + # self.frame_queue.get() # remove old frames + #self.frame_queue.put(pixmap_orig) end_time = time.time() - logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") + #logger.info(f"capture time: {capture_time - start_time}, total time: {end_time - start_time}") def sceneKey(self, event): if ( @@ -4370,7 +4388,7 @@ def parkGripperCB(self): def restartServerCB(self): if self.controlEnabled(): msg = "Desperation move. Are you sure?" - self.timerSample.stop() + #self.timerSample.stop() reply = QtWidgets.QMessageBox.question( self, "Message", @@ -4378,7 +4396,7 @@ def restartServerCB(self): QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No, ) - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample.start(SAMPLE_TIMER_DELAY) if reply == QtWidgets.QMessageBox.Yes: if daq_utils.beamline == "fmx" or daq_utils.beamline == "amx": restart_pv = PV(daq_utils.beamlineComm + "RestartServerSignal") @@ -4404,9 +4422,9 @@ def closePhotonShutterCB(self): self.photonShutterClose_pv.put(1) def removePuckCB(self): - self.timerSample.stop() + #self.timerSample.stop() dewarPos, ok = DewarDialog.getDewarPos(parent=self, action="remove") - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample.start(SAMPLE_TIMER_DELAY) def setVectorPointCB(self, pointName): """Callback function to update a vector point @@ -4459,13 +4477,13 @@ def clearVectorCB(self): def puckToDewarCB(self): while 1: - self.timerSample.stop() + #self.timerSample.stop() puckName, ok = PuckDialog.getPuckName() - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample.start(SAMPLE_TIMER_DELAY) if ok: - self.timerSample.stop() + #self.timerSample.stop() dewarPos, ok = DewarDialog.getDewarPos(parent=self, action="add") - self.timerSample.start(SAMPLE_TIMER_DELAY) + #self.timerSample.start(SAMPLE_TIMER_DELAY) if ok and dewarPos is not None and puckName is not None: ipos = int(dewarPos) + 1 db_lib.insertIntoContainer( diff --git a/threads.py b/threads.py index d92e3330..389ca057 100644 --- a/threads.py +++ b/threads.py @@ -9,6 +9,10 @@ import logging from config_params import SERVER_CHECK_DELAY import raddoseLib +import cv2 +import time +import numpy as np +import requests logger = logging.getLogger() @@ -35,19 +39,29 @@ def camera_refresh(self): self.showing_error = True if self.camera_object: - # https://stackoverflow.com/questions/30032063/opencv-videocapture-lag-due-to-the-capture-buffer - self.camera_object.set(cv2.CAP_PROP_POS_FRAMES, 0) - retval,self.currentFrame = self.camera_object.read() + + # Initialize a variable to store the most recent frame + most_recent_frame = None + + timeout = self.delay / 1000.0 + start_time = time.time() + if self.camera_object.grab(): + retval, frame = self.camera_object.retrieve() + if retval: + most_recent_frame = frame + self.currentFrame = most_recent_frame + if self.currentFrame is None: - logger.debug('no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct') + #logger.debug('no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct') return + height,width=self.currentFrame.shape[:2] qimage= QtGui.QImage(self.currentFrame,width,height,3*width,QtGui.QImage.Format_RGB888) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) if self.width and self.height: pixmap_orig = pixmap_orig.scaled(self.width, self.height) - + if not self.showing_error: self.frame_ready.emit(pixmap_orig) @@ -58,6 +72,10 @@ def __init__(self, *args, delay=1000, url='', camera_object=None, width=None, he self.height = height self.url = url self.camera_object = camera_object + if self.camera_object: + self.camera_object = cv2.VideoCapture(camera_object) #camera_object + self.camera_object.set(cv2.CAP_PROP_BUFFERSIZE, 1) + self.showing_error = False self.is_running = True QThread.__init__(self, *args, **kwargs) @@ -69,6 +87,7 @@ def run(self): while self.is_running: self.camera_refresh() self.msleep(self.delay) + def stop(self): self.is_running = False From 6618309b1a6f54d6c83d0a9977c51100da9b1639 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 27 Sep 2023 12:59:13 -0400 Subject: [PATCH 151/234] logging error fixed --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index cc9dc89a..1fb1e5c1 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3700,7 +3700,7 @@ def standardDaq(currentRequest): start_time = time.time() logger.info(f"Configuring detector for standard collection with file_prefix {file_prefix} and data_directory_name {data_directory_name}") flyer.configure_detector(file_prefix, data_directory_name) - logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}, num_images_per_file {num_images_per_file}") + logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}") flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, file_prefix, data_directory_name, file_number_start, x_beam, y_beam, wavelength, det_distance_m) From 116f61fca91fc5022db43d6a7ec7cb60a4b00dd0 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 13:58:05 -0400 Subject: [PATCH 152/234] Revert "Revert "add aperture device"" This reverts commit a70ad3794297f5e604e2a5d6aa6c3bf2cb1dd113. --- devices.py | 14 +++++++++++++- gui/control_main.py | 13 ++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/devices.py b/devices.py index 45f742e8..737ba3c3 100644 --- a/devices.py +++ b/devices.py @@ -92,7 +92,7 @@ def process_ret(self, ret): elif "NULL" in line: print(f"null error: {line}") return line - elif "EVT:" in line: + elif "EVT:" in line: # print here if you want to see the events pass #return self.process_evt(line) class LightDevice(Device): @@ -271,6 +271,18 @@ def raster_scan(self, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_table_centering, use_fast_mesh_scans] return self.exporter.cmd(command, param_list) + +class MD2ApertureDevice(MD2SimpleHVDevice): + # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" + current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_diameter') + diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') + + def get_diameter_list(self): + return self.diameters.get() + + def set_diameter(self, diameter): + if diameter in self.get_diameter_list(): + self.current_index.put(self.get_diameter_list().index(diameter)) class ShutterDevice(Device): control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal diff --git a/gui/control_main.py b/gui/control_main.py index f0390a2a..87334474 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -22,7 +22,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice, CameraDevice, MD2Device, LightDevice +from devices import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice import daq_utils import db_lib @@ -563,7 +563,10 @@ def createSampleTab(self): setTransButton = QtWidgets.QPushButton("Set Trans") setTransButton.clicked.connect(self.setTransCB) beamsizeLabel = QtWidgets.QLabel("BeamSize:") - beamSizeOptionList = BEAMSIZE_OPTIONS.keys() + if daq_utils.beamline == "nyx": + beamSizeOptionList = self.aperture.get_diameter_list() + else: + beamSizeOptionList = BEAMSIZE_OPTIONS.keys() self.beamsizeComboBox = QtWidgets.QComboBox(self) self.beamsizeComboBox.addItems(beamSizeOptionList) self.beamsizeComboBox.setCurrentIndex(int(self.beamSize_pv.get())) @@ -2623,7 +2626,10 @@ def protoRadioToggledCB(self, text): pass def beamsizeComboActivatedCB(self, text): - self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) + if daq_utils.beamline == "nyx": + self.aperture.set_diameter(text) + else: + self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) def protoComboActivatedCB(self, text): self.showProtParams() @@ -4941,6 +4947,7 @@ def initOphyd(self): self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") + self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") else: pass From d2dba32a0ea49d16e1de2575fb6c2f68db8b69d0 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 14:14:39 -0400 Subject: [PATCH 153/234] hide unused elements --- gui/control_main.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 87334474..e53cc2ef 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1527,27 +1527,28 @@ def createSampleTab(self): # 12/19 - uncomment this to expose the PyMCA XRF interface. It's not connected to anything. self.zoomLevelToggledCB("Zoom1") - if daq_utils.beamline == "nyx": # Temporarily disabling unusued buttons on NYX - self.protoRasterRadio.setDisabled(True) - self.protoStandardRadio.setDisabled(True) - self.protoVectorRadio.setDisabled(True) - self.protoOtherRadio.setDisabled(True) - self.autoProcessingCheckBox.setDisabled(True) - self.fastEPCheckBox.setDisabled(True) - self.dimpleCheckBox.setDisabled(True) - self.centeringComboBox.setDisabled(True) - self.beamsizeComboBox.setDisabled(True) - annealButton.setDisabled(True) - centerLoopButton.setDisabled(True) - clearGraphicsButton.setDisabled(True) - saveCenteringButton.setDisabled(True) - selectAllCenteringButton.setDisabled(True) - snapshotButton.setDisabled(True) + if daq_utils.beamline == "nyx": # hiding unused GUI elements + self.protoRasterRadio.setVisible(False) + self.protoStandardRadio.setVisible(False) + self.protoVectorRadio.setVisible(False) + self.protoOtherRadio.setVisible(False) + self.autoProcessingCheckBox.setVisible(False) + self.fastEPCheckBox.setVisible(False) + self.dimpleCheckBox.setVisible(False) + self.centeringComboBox.setVisible(False) + self.beamsizeComboBox.setVisible(False) + annealButton.setVisible(False) + centerLoopButton.setVisible(False) + clearGraphicsButton.setVisible(False) + saveCenteringButton.setVisible(False) + selectAllCenteringButton.setVisible(False) + snapshotButton.setVisible(False) + self.vidActionDefineCenterRadio.setVisible(False) self.hideRastersCheckBox.setEnabled(True) self.vidActionC2CRadio.setEnabled(True) self.vidActionRasterExploreRadio.setEnabled(True) self.vidActionRasterDefRadio.setEnabled(True) - self.vidActionDefineCenterRadio.setDisabled(True) + #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) From de73f4abd7179f13adf1a88142c5673d60668f9e Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 14:14:58 -0400 Subject: [PATCH 154/234] testing new gui elements --- gui/control_main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index e53cc2ef..4472bf6c 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -4943,12 +4943,18 @@ def pauseButtonStateCB(self, value=None, char_value=None, **kw): def initOphyd(self): if daq_utils.beamline == "nyx": + # initialize devices self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") + + # initialize parameters for gui elements + print(f"Aperature diameters: {self.aperture.diameters.get()}") + logger.info(f"Aperature diameters: {self.aperture.diameters.get()}") + logger.info(f"Zoom level: {self.camera.zoom.get()}") else: pass From 78052d6bf7a7df8512fb4ee6b1b096750ab7a737 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 14:26:18 -0400 Subject: [PATCH 155/234] fix md2 device name --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 4472bf6c..a7fae701 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -4946,7 +4946,7 @@ def initOphyd(self): # initialize devices self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") - self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="camera") + self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") From 3cc8965e34430b37ae4a3c67f372e2533795d8c3 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 14:34:55 -0400 Subject: [PATCH 156/234] remove aperture device class extension --- devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices.py b/devices.py index 737ba3c3..c8b7fa3b 100644 --- a/devices.py +++ b/devices.py @@ -272,9 +272,9 @@ def raster_scan(self, invert_direction, use_table_centering, use_fast_mesh_scans] return self.exporter.cmd(command, param_list) -class MD2ApertureDevice(MD2SimpleHVDevice): +class MD2ApertureDevice(Device): # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" - current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_diameter') + current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_index') diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') def get_diameter_list(self): From f384d46696c8bdb1f9a344fc14cb974460f21d0a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 15:21:01 -0400 Subject: [PATCH 157/234] Aperture Device fixes --- devices.py | 2 ++ gui/control_main.py | 15 ++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/devices.py b/devices.py index c8b7fa3b..23670e27 100644 --- a/devices.py +++ b/devices.py @@ -278,6 +278,8 @@ class MD2ApertureDevice(Device): diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') def get_diameter_list(self): + # must format list for GUI + # also, can't currently get from PV return self.diameters.get() def set_diameter(self, diameter): diff --git a/gui/control_main.py b/gui/control_main.py index a7fae701..5b27768b 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -564,12 +564,15 @@ def createSampleTab(self): setTransButton.clicked.connect(self.setTransCB) beamsizeLabel = QtWidgets.QLabel("BeamSize:") if daq_utils.beamline == "nyx": - beamSizeOptionList = self.aperture.get_diameter_list() + # beamSizeOptionList = self.aperture.get_diameter_list() PV not working, needs investigation + beamSizeOptionList = ["5", "10", "20", "30", "50", "100"] + current_index = self.aperture.current_index.get() else: beamSizeOptionList = BEAMSIZE_OPTIONS.keys() + current_index = int(self.beamSize_pv.get()) self.beamsizeComboBox = QtWidgets.QComboBox(self) self.beamsizeComboBox.addItems(beamSizeOptionList) - self.beamsizeComboBox.setCurrentIndex(int(self.beamSize_pv.get())) + self.beamsizeComboBox.setCurrentIndex(current_index) self.beamsizeComboBox.activated[str].connect(self.beamsizeComboActivatedCB) if daq_utils.beamline == "amx" or self.energy_pv.get() < 9000: self.beamsizeComboBox.setEnabled(False) @@ -1536,7 +1539,6 @@ def createSampleTab(self): self.fastEPCheckBox.setVisible(False) self.dimpleCheckBox.setVisible(False) self.centeringComboBox.setVisible(False) - self.beamsizeComboBox.setVisible(False) annealButton.setVisible(False) centerLoopButton.setVisible(False) clearGraphicsButton.setVisible(False) @@ -2628,7 +2630,7 @@ def protoRadioToggledCB(self, text): def beamsizeComboActivatedCB(self, text): if daq_utils.beamline == "nyx": - self.aperture.set_diameter(text) + self.aperture.current_index.mv(int(text)) else: self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) @@ -4950,11 +4952,6 @@ def initOphyd(self): self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") - - # initialize parameters for gui elements - print(f"Aperature diameters: {self.aperture.diameters.get()}") - logger.info(f"Aperature diameters: {self.aperture.diameters.get()}") - logger.info(f"Zoom level: {self.camera.zoom.get()}") else: pass From e920b09a4f690eb66cb08d99b495424a92a051cb Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 15:27:43 -0400 Subject: [PATCH 158/234] fix combo box setter --- gui/control_main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 5b27768b..ddce72c2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -2630,7 +2630,8 @@ def protoRadioToggledCB(self, text): def beamsizeComboActivatedCB(self, text): if daq_utils.beamline == "nyx": - self.aperture.current_index.mv(int(text)) + index = self.beamsizeComboBox.findText(str(text)) + self.aperture.current_index.set(index) else: self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) From b91165fc6b87e131ce1b005e820bf7bf1ca808f4 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 15:30:14 -0400 Subject: [PATCH 159/234] use put instead of set --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index ddce72c2..f86995ef 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -2631,7 +2631,7 @@ def protoRadioToggledCB(self, text): def beamsizeComboActivatedCB(self, text): if daq_utils.beamline == "nyx": index = self.beamsizeComboBox.findText(str(text)) - self.aperture.current_index.set(index) + self.aperture.current_index.put(index) else: self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) From b8f251ced9b9fa7b6fb9a25a5f958fdef9305f08 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 15:33:09 -0400 Subject: [PATCH 160/234] limit button size --- gui/control_main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index f86995ef..d5302ea8 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1319,6 +1319,7 @@ def createSampleTab(self): clearGraphicsButton.clicked.connect(self.eraseCB) self.click3Button = QtWidgets.QPushButton("3-Click\nCenter") self.click3Button.clicked.connect(self.center3LoopCB) + self.threeClickCount = 0 saveCenteringButton = QtWidgets.QPushButton("Save\nCenter") saveCenteringButton.clicked.connect(self.saveCenterCB) @@ -1332,6 +1333,7 @@ def createSampleTab(self): hBoxSampleAlignLayout.addWidget(self.click3Button) hBoxSampleAlignLayout.addWidget(snapshotButton) hBoxSampleAlignLayout.addWidget(self.hideRastersCheckBox) + self.click3Button.setMaximumSize(self.click3Button.sizeHint()) hBoxRadioLayout100 = QtWidgets.QHBoxLayout() vidActionLabel = QtWidgets.QLabel("Video Click Mode:") self.vidActionRadioGroup = QtWidgets.QButtonGroup() From 22aa887887dcf035938cd3f842c3a4d9b533c9d8 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 29 Sep 2023 15:42:15 -0400 Subject: [PATCH 161/234] fix beam size list --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index d5302ea8..a6d2749a 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -565,7 +565,7 @@ def createSampleTab(self): beamsizeLabel = QtWidgets.QLabel("BeamSize:") if daq_utils.beamline == "nyx": # beamSizeOptionList = self.aperture.get_diameter_list() PV not working, needs investigation - beamSizeOptionList = ["5", "10", "20", "30", "50", "100"] + beamSizeOptionList = ["10", "20", "30", "50", "100"] current_index = self.aperture.current_index.get() else: beamSizeOptionList = BEAMSIZE_OPTIONS.keys() From fa639eaaa268a0fe9e71cbf1f7c1678f2df4b579 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 3 Oct 2023 15:25:17 -0400 Subject: [PATCH 162/234] Fix screen scaling for MD2 --- gui/control_main.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index a6d2749a..a0f5ba2e 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3420,27 +3420,32 @@ def screenXPixels2microns(self, pixels): md2_img_width = self.md2.center_pixel_x.get() * 2.0 lsdc_img_width = daq_utils.screenPixX img_scale_factor = md2_img_width / lsdc_img_width - pixels_per_micron = self.camera.scale_x.get() * 1000 + pixels_per_mm = self.camera.scale_x.get() + pixels_per_micron = pixels_per_mm / 1000.0 return float(pixels * img_scale_factor) / pixels_per_micron + print(f"pixels per micron = {pixels_per_micron}") def screenYPixels2microns(self, pixels): md2_img_height = self.md2.center_pixel_y.get() * 2.0 lsdc_img_height = daq_utils.screenPixY - pixels_per_micron = self.camera.scale_y.get() * 1000 + pixels_per_mm = self.camera.scale_y.get() + pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_height / lsdc_img_height return float(pixels * img_scale_factor) / pixels_per_micron def screenXmicrons2pixels(self, microns): md2_img_width = self.md2.center_pixel_x.get() * 2.0 lsdc_img_width = daq_utils.screenPixX - pixels_per_micron = self.camera.scale_x.get() * 1000 + pixels_per_mm = self.camera.scale_x.get() + pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_width / lsdc_img_width return float(microns * pixels_per_micron) / img_scale_factor def screenYmicrons2pixels(self, microns): md2_img_height = self.md2.center_pixel_y.get() * 2.0 lsdc_img_height = daq_utils.screenPixY - pixels_per_micron = self.camera.scale_y.get() * 1000 + pixels_per_mm = self.camera.scale_y.get() + pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_height / lsdc_img_height return float(microns * pixels_per_micron) / img_scale_factor From fe6491468325890b585675ab88c64bdbf37fd522 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 3 Oct 2023 15:26:55 -0400 Subject: [PATCH 163/234] inverted unit, documentation wrong --- gui/control_main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index a0f5ba2e..648dfce9 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3420,7 +3420,7 @@ def screenXPixels2microns(self, pixels): md2_img_width = self.md2.center_pixel_x.get() * 2.0 lsdc_img_width = daq_utils.screenPixX img_scale_factor = md2_img_width / lsdc_img_width - pixels_per_mm = self.camera.scale_x.get() + pixels_per_mm = 1 / self.camera.scale_x.get() pixels_per_micron = pixels_per_mm / 1000.0 return float(pixels * img_scale_factor) / pixels_per_micron print(f"pixels per micron = {pixels_per_micron}") @@ -3428,7 +3428,7 @@ def screenXPixels2microns(self, pixels): def screenYPixels2microns(self, pixels): md2_img_height = self.md2.center_pixel_y.get() * 2.0 lsdc_img_height = daq_utils.screenPixY - pixels_per_mm = self.camera.scale_y.get() + pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_height / lsdc_img_height return float(pixels * img_scale_factor) / pixels_per_micron @@ -3436,7 +3436,7 @@ def screenYPixels2microns(self, pixels): def screenXmicrons2pixels(self, microns): md2_img_width = self.md2.center_pixel_x.get() * 2.0 lsdc_img_width = daq_utils.screenPixX - pixels_per_mm = self.camera.scale_x.get() + pixels_per_mm = 1 / self.camera.scale_x.get() pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_width / lsdc_img_width return float(microns * pixels_per_micron) / img_scale_factor @@ -3444,7 +3444,7 @@ def screenXmicrons2pixels(self, microns): def screenYmicrons2pixels(self, microns): md2_img_height = self.md2.center_pixel_y.get() * 2.0 lsdc_img_height = daq_utils.screenPixY - pixels_per_mm = self.camera.scale_y.get() + pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 img_scale_factor = md2_img_height / lsdc_img_height return float(microns * pixels_per_micron) / img_scale_factor From c8bb8487d5981bf30e4292b7d90206d5839641af Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Tue, 3 Oct 2023 16:47:29 -0400 Subject: [PATCH 164/234] External Series update --- md2_flyers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 75de23bc..b98d247d 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -64,9 +64,10 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im file_prefix_minus_directory = file_prefix_minus_directory.split("/")[-1] self.detector.cam.acquire_time.put(exposure_per_image) self.detector.cam.acquire_period.put(exposure_per_image) - self.detector.cam.num_triggers.put(total_num_images) + self.detector.cam.num_triggers.put(1) + self.detector.cam.num_images.put(total_num_images) self.detector.cam.trigger_mode.put( - EXTERNAL_ENABLE + EXTERNAL_SERIES ) # must be external_enable to get the correct number of triggers and stop acquire self.detector.cam.file_path.put(data_directory_name) self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") From d5f1560801b8d54ed34791610482867bdcd5a757 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Sun, 8 Oct 2023 02:28:24 -0400 Subject: [PATCH 165/234] Remove anneal labels --- gui/control_main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index 648dfce9..abe1b00b 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1547,6 +1547,8 @@ def createSampleTab(self): saveCenteringButton.setVisible(False) selectAllCenteringButton.setVisible(False) snapshotButton.setVisible(False) + annealTimeLabel.setVisible(False) + self.annealTime_ledit.setVisible(False) self.vidActionDefineCenterRadio.setVisible(False) self.hideRastersCheckBox.setEnabled(True) self.vidActionC2CRadio.setEnabled(True) From 998e06385806038a088e8fb09f5c18b66ab21a33 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 11 Oct 2023 14:59:16 -0400 Subject: [PATCH 166/234] Use daq_utils image dimension definitions --- daq_utils.py | 3 ++- gui/control_main.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/daq_utils.py b/daq_utils.py index 8c4d79b6..00b18c78 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -53,7 +53,8 @@ def setBlConfig(param, value, beamline=beamline): def init_environment(): global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle,exporter_enabled - + setBlConfig("highMagPixX", "1224") + setBlConfig("highMagPixY", "1024") setBlConfig("highMagCamURL", "http://10.67.147.26:3908/video_feed2") owner = getpass.getuser() primaryDewarName = getBlConfig("primaryDewarName") diff --git a/gui/control_main.py b/gui/control_main.py index abe1b00b..c0117d3e 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3419,7 +3419,7 @@ def getCurrentFOV(self): return fov def screenXPixels2microns(self, pixels): - md2_img_width = self.md2.center_pixel_x.get() * 2.0 + md2_img_width = daq_utils.highMagPixX * 2.0 lsdc_img_width = daq_utils.screenPixX img_scale_factor = md2_img_width / lsdc_img_width pixels_per_mm = 1 / self.camera.scale_x.get() @@ -3428,7 +3428,7 @@ def screenXPixels2microns(self, pixels): print(f"pixels per micron = {pixels_per_micron}") def screenYPixels2microns(self, pixels): - md2_img_height = self.md2.center_pixel_y.get() * 2.0 + md2_img_height = daq_utils.highMagPixY * 2.0 lsdc_img_height = daq_utils.screenPixY pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 @@ -3436,7 +3436,7 @@ def screenYPixels2microns(self, pixels): return float(pixels * img_scale_factor) / pixels_per_micron def screenXmicrons2pixels(self, microns): - md2_img_width = self.md2.center_pixel_x.get() * 2.0 + md2_img_width = daq_utils.highMagPixX * 2.0 lsdc_img_width = daq_utils.screenPixX pixels_per_mm = 1 / self.camera.scale_x.get() pixels_per_micron = pixels_per_mm / 1000.0 @@ -3444,7 +3444,7 @@ def screenXmicrons2pixels(self, microns): return float(microns * pixels_per_micron) / img_scale_factor def screenYmicrons2pixels(self, microns): - md2_img_height = self.md2.center_pixel_y.get() * 2.0 + md2_img_height = daq_utils.highMagPixY * 2.0 lsdc_img_height = daq_utils.screenPixY pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 From c4707ba1f6d83cd5f136c856ebb5466f404b7c03 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 11 Oct 2023 15:06:33 -0400 Subject: [PATCH 167/234] Add image ratio and beam center functions --- gui/control_main.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index c0117d3e..18ffef83 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3419,38 +3419,46 @@ def getCurrentFOV(self): return fov def screenXPixels2microns(self, pixels): - md2_img_width = daq_utils.highMagPixX * 2.0 - lsdc_img_width = daq_utils.screenPixX - img_scale_factor = md2_img_width / lsdc_img_width + img_scale_factor = self.getMD2ImageXRatio() pixels_per_mm = 1 / self.camera.scale_x.get() pixels_per_micron = pixels_per_mm / 1000.0 return float(pixels * img_scale_factor) / pixels_per_micron print(f"pixels per micron = {pixels_per_micron}") def screenYPixels2microns(self, pixels): - md2_img_height = daq_utils.highMagPixY * 2.0 - lsdc_img_height = daq_utils.screenPixY pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = md2_img_height / lsdc_img_height + img_scale_factor = self.getMD2ImageYRatio() return float(pixels * img_scale_factor) / pixels_per_micron def screenXmicrons2pixels(self, microns): - md2_img_width = daq_utils.highMagPixX * 2.0 - lsdc_img_width = daq_utils.screenPixX pixels_per_mm = 1 / self.camera.scale_x.get() pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = md2_img_width / lsdc_img_width + img_scale_factor = self.getMD2ImageXRatio() return float(microns * pixels_per_micron) / img_scale_factor def screenYmicrons2pixels(self, microns): - md2_img_height = daq_utils.highMagPixY * 2.0 - lsdc_img_height = daq_utils.screenPixY pixels_per_mm = 1 / self.camera.scale_y.get() pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = md2_img_height / lsdc_img_height + img_scale_factor = self.getMD2ImageYRatio() return float(microns * pixels_per_micron) / img_scale_factor + def getMD2ImageXRatio(self): + md2_img_width = daq_utils.highMagPixX + lsdc_img_width = daq_utils.screenPixX + return float(md2_img_width) / float(lsdc_img_width) + + def getMD2ImageYRatio(self): + md2_img_height = daq_utils.highMagPixY + lsdc_img_height = daq_utils.screenPixY + return float(md2_img_height) / float(lsdc_img_height) + + def getMD2BeamCenterX(self): + return self.md2.center_pixel_x.get() / self.getMD2ImageXRatio() + + def getMD2BeamCenterY(self): + return self.md2.center_pixel_y.get() / self.getMD2ImageYRatio() + def definePolyRaster( self, raster_w, raster_h, stepsizeXPix, stepsizeYPix, point_x, point_y, stepsize ): # all come in as pixels, raster_w and raster_h are bounding box of drawn graphic From 125e7b427b95d58430db6932933c054cc2542f14 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 11 Oct 2023 15:09:29 -0400 Subject: [PATCH 168/234] adjust beam center --- gui/control_main.py | 68 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 18ffef83..7f9afefc 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1108,8 +1108,8 @@ def createSampleTab(self): self.centerMarker.setFont(font) self.scene.addItem(self.centerMarker) self.centerMarker.setPos( - daq_utils.screenPixCenterX - self.centerMarkerCharOffsetX, - daq_utils.screenPixCenterY - self.centerMarkerCharOffsetY, + self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX, + self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY, ) self.zoomRadioGroup = QtWidgets.QButtonGroup() self.zoom1Radio = QtWidgets.QRadioButton("Mag1") @@ -1742,8 +1742,8 @@ def zoomLevelComboActivatedCB(self, identifier): def zoomLevelToggledCB(self, identifier): fov = {} - zoomedCursorX = daq_utils.screenPixCenterX - self.centerMarkerCharOffsetX - zoomedCursorY = daq_utils.screenPixCenterY - self.centerMarkerCharOffsetY + zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): self.flushBuffer(self.captureLowMagZoom) self.capture = self.captureLowMagZoom @@ -1751,18 +1751,18 @@ def zoomLevelToggledCB(self, identifier): fov["y"] = daq_utils.lowMagFOVy / 2.0 unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY - if unzoomedCursorX * 2.0 < daq_utils.screenPixCenterX: + if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < daq_utils.screenPixCenterY: + if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - daq_utils.screenPixCenterX - > daq_utils.screenPixCenterX / 2 + unzoomedCursorX - self.getMD2BeamCenterX() + > self.getMD2BeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - daq_utils.screenPixCenterY - > daq_utils.screenPixCenterY / 2 + unzoomedCursorY - self.getMD2BeamCenterY() + > self.getMD2BeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -1810,18 +1810,18 @@ def zoomLevelToggledCB(self, identifier): unzoomedCursorY = ( self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY ) - if unzoomedCursorX * 2.0 < daq_utils.screenPixCenterX: + if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < daq_utils.screenPixCenterY: + if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - daq_utils.screenPixCenterX - > daq_utils.screenPixCenterX / 2 + unzoomedCursorX - self.getMD2BeamCenterX() + > self.getMD2BeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - daq_utils.screenPixCenterY - > daq_utils.screenPixCenterY / 2 + unzoomedCursorY - self.getMD2BeamCenterY() + > self.getMD2BeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -1982,23 +1982,23 @@ def processROIChange(self, posRBV, ID): pass def processLowMagCursorChange(self, posRBV, ID): - zoomedCursorX = daq_utils.screenPixCenterX - self.centerMarkerCharOffsetX - zoomedCursorY = daq_utils.screenPixCenterY - self.centerMarkerCharOffsetY + zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): # lowmagzoom unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY - if unzoomedCursorX * 2.0 < daq_utils.screenPixCenterX: + if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < daq_utils.screenPixCenterY: + if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - daq_utils.screenPixCenterX - > daq_utils.screenPixCenterX / 2 + unzoomedCursorX - self.getMD2BeamCenterX() + > self.getMD2BeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - daq_utils.screenPixCenterY - > daq_utils.screenPixCenterY / 2 + unzoomedCursorY - self.getMD2BeamCenterY() + > self.getMD2BeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -2033,8 +2033,8 @@ def processLowMagCursorChange(self, posRBV, ID): ) def processHighMagCursorChange(self, posRBV, ID): - zoomedCursorX = daq_utils.screenPixCenterX - self.centerMarkerCharOffsetX - zoomedCursorY = daq_utils.screenPixCenterY - self.centerMarkerCharOffsetY + zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY if self.zoom4Radio.isChecked(): # highmagzoom unzoomedCursorX = ( self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX @@ -2042,18 +2042,18 @@ def processHighMagCursorChange(self, posRBV, ID): unzoomedCursorY = ( self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY ) - if unzoomedCursorX * 2.0 < daq_utils.screenPixCenterX: + if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < daq_utils.screenPixCenterY: + if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - daq_utils.screenPixCenterX - > daq_utils.screenPixCenterX / 2 + unzoomedCursorX - self.getMD2BeamCenterX() + > self.getMD2BeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - daq_utils.screenPixCenterY - > daq_utils.screenPixCenterY / 2 + unzoomedCursorY - self.getMD2BeamCenterY() + > self.getMD2BeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -3780,10 +3780,10 @@ def pixelSelect(self, event): self.drawInteractiveRasterCB() return fov = self.getCurrentFOV() - correctedC2C_x = daq_utils.screenPixCenterX + ( + correctedC2C_x = self.getMD2BeamCenterX() + ( x_click - (self.centerMarker.x() + self.centerMarkerCharOffsetX) ) - correctedC2C_y = daq_utils.screenPixCenterY + ( + correctedC2C_y = self.getMD2BeamCenterY() + ( y_click - (self.centerMarker.y() + self.centerMarkerCharOffsetY) ) From bebdce43e3ead5e06b045fb0d611e54b041289cf Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 11 Oct 2023 20:25:21 -0400 Subject: [PATCH 169/234] Adjust beam center marker on zoom level change. --- gui/control_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/control_main.py b/gui/control_main.py index 7f9afefc..d37dec59 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1736,6 +1736,7 @@ def flushBuffer(self, vidStream): def zoomLevelComboActivatedCB(self, identifier): self.camera.zoom.put(identifier) + self.centerMarker.setPos(self.getMD2BeamCenterX()-self.centerMarkerCharOffsetX, self.getMD2BeamCenterY()-self.centerMarkerCharOffsetY) #self.flushBuffer(self.capture) #self.capture.release() #self.capture = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) From ac63759116b8e923f5170bc3479550a71c2509ce Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Thu, 12 Oct 2023 20:34:54 -0400 Subject: [PATCH 170/234] testing raster values --- daq_macros.py | 54 ++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 1fb1e5c1..eb7b0eee 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3798,42 +3798,48 @@ def rasterDaq(rasterReqID): data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, start_omega, start_x, start_y, start_z, omegaRad, number_of_lines, numsteps, totalImages, rows = params_from_raster_req_id(rasterReqID) rasterRowResultsList = [{} for i in range(0,number_of_lines)] processedRasterRowCount = 0 - rasterEncoderMap = {} - - xbeam = getPvDesc("beamCenterX") - ybeam = getPvDesc("beamCenterY") - rasterRequest = db_lib.getRequestByID(rasterReqID) reqObj = rasterRequest["request_obj"] parentReqID = reqObj["parentReqID"] - parentReqProtocol = "" if (parentReqID != -1): parentRequest = db_lib.getRequestByID(parentReqID) parentReqObj = parentRequest["request_obj"] - parentReqProtocol = parentReqObj["protocol"] detDist = parentReqObj["detDist"] rasterFilePrefix = dataFilePrefix + "_Raster" - logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") + #logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") logger.info(f"req_obj: {reqObj}") # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) - #omega_range - line_range = img_width_per_cell * num_cells - total_uturn_range = line_range * num_lines - #start_y - #start_z - #start_cx - #start_cy - #number_of_lines - #frames_per_lines - #exposure_time = exptimePerCell * totalImages - #invert_direction = True - #use_centring_table = True - #use_fast_mesh_scans = True + line_range = img_width_per_cell * numsteps + total_uturn_range = line_range * number_of_lines + start_y = rows[0]['y'] + start_z = rows[0]['z'] + start_cx = 0 + start_cy = 0 + frames_per_line = numsteps + total_exposure_time = exptimePerCell * totalImages + invert_direction = True + use_centring_table = True + use_fast_mesh_scans = True + logger.info(f"omega_range = {omegaRad}") + logger.info(f"line_range = {line_range}") + logger.info(f"total_uturn_range = {total_uturn_range}") + logger.info(f"start_omega = {start_omega}") + logger.info(f"start_y = {start_y}") + logger.info(f"start_z = {start_z}") + logger.info(f"start_cx = {start_cx}") + logger.info(f"start_cy = {start_cy}") + logger.info(f"number_of_lines = {number_of_lines}") + logger.info(f"frames_per_line = {frames_per_line}") + logger.info(f"total_exposure_time = {total_exposure_time}") + logger.info(f"invert_direction = {invert_direction}") + logger.info(f"use_centring_table = {use_centring_table}") + logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") + #if raster_flyer.detector.cam.armed.get() == 1: # daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' @@ -3843,9 +3849,9 @@ def rasterDaq(rasterReqID): # return 0 #start_time = time.time() #raster_flyer.configure_detector(rasterFilePrefix, data_directory_name) - #raster_flyer.detector_arm(start_omega, img_width, total_num_images, exposure_per_image, + #raster_flyer.detector_arm(start_omega, img_width_per_cell, total_num_images, exposure_per_image, # file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - # wavelength, det_distance_m, num_images_per_file) + # wavelength, det_distance_m) #def armed_callback(value, old_value, **kwargs): # return (old_value == 0 and value == 1) #arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) @@ -3862,7 +3868,7 @@ def rasterDaq(rasterReqID): #yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states #md2.ready_status().wait(timeout=10) #logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - #raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_lines, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) + #raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) #yield from bp.fly([raster_flyer]) From 90afbc3425fdce635ac48d1cbf83b46e72ac4d63 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 23 Oct 2023 12:24:01 -0400 Subject: [PATCH 171/234] using simple cx and cy positions --- daq_macros.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index eb7b0eee..359acdc4 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3808,18 +3808,16 @@ def rasterDaq(rasterReqID): rasterFilePrefix = dataFilePrefix + "_Raster" - #logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") - logger.info(f"req_obj: {reqObj}") + logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") + #logger.info(f"req_obj: {reqObj}") # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) line_range = img_width_per_cell * numsteps total_uturn_range = line_range * number_of_lines - start_y = rows[0]['y'] - start_z = rows[0]['z'] - start_cx = 0 - start_cy = 0 + start_cx = md2.cx.get() + start_cy = md2.cx.get() frames_per_line = numsteps total_exposure_time = exptimePerCell * totalImages invert_direction = True From 1afda3ff407b3d8e90da88a28be9d991c6e45e36 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 23 Oct 2023 13:20:38 -0400 Subject: [PATCH 172/234] raster updates --- daq_macros.py | 72 +++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 359acdc4..f0e77364 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3795,12 +3795,15 @@ def armed_callback(value, old_value, **kwargs): def rasterDaq(rasterReqID): global rasterRowResultsList,processedRasterRowCount - data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, start_omega, start_x, start_y, start_z, omegaRad, number_of_lines, numsteps, totalImages, rows = params_from_raster_req_id(rasterReqID) + data_directory_name, file_prefix, file_number_start, dataFilePrefix, exposure_per_image, img_width_per_cell, wavelength, detDist, rasterDef, stepsize, start_omega, start_x, start_y, start_z, omegaRad, number_of_lines, numsteps, total_num_images, rows = params_from_raster_req_id(rasterReqID) rasterRowResultsList = [{} for i in range(0,number_of_lines)] processedRasterRowCount = 0 rasterRequest = db_lib.getRequestByID(rasterReqID) reqObj = rasterRequest["request_obj"] parentReqID = reqObj["parentReqID"] + + xbeam = getPvDesc("beamCenterX") + ybeam = getPvDesc("beamCenterY") if (parentReqID != -1): parentRequest = db_lib.getRequestByID(parentReqID) parentReqObj = parentRequest["request_obj"] @@ -3808,7 +3811,7 @@ def rasterDaq(rasterReqID): rasterFilePrefix = dataFilePrefix + "_Raster" - logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exptimePerCell}, {img_width_per_cell}, {wave}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {totalImages}, {rows}") + logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exposure_per_image}, {img_width_per_cell}, {wavelength}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {total_num_images}, {rows}") #logger.info(f"req_obj: {reqObj}") # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) @@ -3816,13 +3819,14 @@ def rasterDaq(rasterReqID): # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) line_range = img_width_per_cell * numsteps total_uturn_range = line_range * number_of_lines - start_cx = md2.cx.get() - start_cy = md2.cx.get() + start_cx = md2.cx.val() + start_cy = md2.cy.val() frames_per_line = numsteps - total_exposure_time = exptimePerCell * totalImages + total_exposure_time = exposure_per_image * total_num_images invert_direction = True use_centring_table = True use_fast_mesh_scans = True + omega_range = 0 logger.info(f"omega_range = {omegaRad}") logger.info(f"line_range = {line_range}") logger.info(f"total_uturn_range = {total_uturn_range}") @@ -3839,35 +3843,35 @@ def rasterDaq(rasterReqID): logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") - #if raster_flyer.detector.cam.armed.get() == 1: - # daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' - # 'should check the most recent collection to determine if it was successful. Cancelling' - # 'this collection, retry when ready.') - # logger.warning("Detector was in the armed state prior to this attempted collection.") - # return 0 - #start_time = time.time() - #raster_flyer.configure_detector(rasterFilePrefix, data_directory_name) - #raster_flyer.detector_arm(start_omega, img_width_per_cell, total_num_images, exposure_per_image, - # file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - # wavelength, det_distance_m) - #def armed_callback(value, old_value, **kwargs): - # return (old_value == 0 and value == 1) - #arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) - #raster_flyer.detector.cam.acquire.put(1) - #govStatus = gov_lib.setGovRobot(gov_robot, "DA") - #arm_status.wait(timeout=10) - #govStatus.wait(timeout=20) - #logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") - #if govStatus.exception(): - # logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") - # return - #flyer.detector.stage() - #start_time = time.time() - #yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - #md2.ready_status().wait(timeout=10) - #logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - #raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) - #yield from bp.fly([raster_flyer]) + if raster_flyer.detector.cam.armed.get() == 1: + daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' + 'should check the most recent collection to determine if it was successful. Cancelling' + 'this collection, retry when ready.') + logger.warning("Detector was in the armed state prior to this attempted collection.") + return 0 + start_time = time.time() + raster_flyer.configure_detector(rasterFilePrefix, data_directory_name) + raster_flyer.detector_arm(start_omega, img_width_per_cell, total_num_images, exposure_per_image, + file_prefix, data_directory_name, file_number_start, xbeam, ybeam, + wavelength, detDist) + def armed_callback(value, old_value, **kwargs): + return (old_value == 0 and value == 1) + arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) + raster_flyer.detector.cam.acquire.put(1) + govStatus = gov_lib.setGovRobot(gov_robot, "DA") + arm_status.wait(timeout=10) + govStatus.wait(timeout=20) + logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") + if govStatus.exception(): + logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") + return + flyer.detector.stage() + start_time = time.time() + yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states + md2.ready_status().wait(timeout=10) + logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") + raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) + yield from bp.fly([raster_flyer]) From ab3de1fcf64ea544646449715a715453299e1005 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 23 Oct 2023 13:28:44 -0400 Subject: [PATCH 173/234] consistently using correct naming, doc needs fix --- md2_flyers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index b98d247d..b18251b6 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -242,7 +242,7 @@ def kickoff(self): start_cx=self.collection_params["start_cx"], start_cy=self.collection_params["start_cy"], number_of_lines=self.collection_params["number_of_lines"], - frames_per_lines=self.collection_params["frames_per_lines"], + frames_per_line=self.collection_params["frames_per_line"], exposure_time=self.collection_params["exposure_time"], invert_direction=self.collection_params["invert_direction"], use_centring_table=self.collection_params["use_centring_table"], @@ -250,7 +250,7 @@ def kickoff(self): logger.info(f"md2_msg: {md2_msg}") return NullStatus() - def update_parameters(self, omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_lines, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans): + def update_parameters(self, omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans): self.collection_params = { "omega_range": omega_range, "line_range": line_range, @@ -261,7 +261,7 @@ def update_parameters(self, omega_range, line_range, total_uturn_range, start_om "start_cx": start_cx, "start_cy": start_cy, "number_of_lines": number_of_lines, - "frames_per_lines": frames_per_lines, + "frames_per_line": frames_per_line, "exposure_time": exposure_time, "invert_direction": invert_direction, "use_centring_table": use_centring_table, From 0b0e8bff9c10424815a4c1b6905ce6b0512d739a Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 23 Oct 2023 13:31:41 -0400 Subject: [PATCH 174/234] centring table variable --- devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices.py b/devices.py index 23670e27..32ad69c1 100644 --- a/devices.py +++ b/devices.py @@ -244,7 +244,7 @@ def raster_scan(self, frames_per_line=5, # int: number of frames on the horizontal range. exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_table_centering=True, # boolean: true to use the centring table to do the pitch movements. + use_centring_table=True, # boolean: true to use the centring table to do the pitch movements. use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). ): @@ -269,7 +269,7 @@ def raster_scan(self, # "invert_direction", "use_centring_table", "use_fast_mesh_scans" param_list = [omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, - invert_direction, use_table_centering, use_fast_mesh_scans] + invert_direction, use_centring_table, use_fast_mesh_scans] return self.exporter.cmd(command, param_list) class MD2ApertureDevice(Device): From 563adde7fbd7e1f96304157c5608449c64bdae80 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Wed, 1 Nov 2023 23:45:46 -0400 Subject: [PATCH 175/234] Adjusting 3-CC position for crosshair offset --- gui/control_main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index d37dec59..83be7339 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3799,6 +3799,8 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: + correctedC2C_x = x_click + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) + correctedC2C_y = y_click + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) lsdc_x = daq_utils.screenPixX lsdc_y = daq_utils.screenPixY md2_x = self.md2.center_pixel_x.get() * 2 From 886d75f7634d806a4a9fc0de53b32749028ff794 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 3 Nov 2023 18:19:50 -0400 Subject: [PATCH 176/234] additional c2c fixes --- gui/control_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 83be7339..89606755 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3782,10 +3782,10 @@ def pixelSelect(self, event): return fov = self.getCurrentFOV() correctedC2C_x = self.getMD2BeamCenterX() + ( - x_click - (self.centerMarker.x() + self.centerMarkerCharOffsetX) + x_click - (self.centerMarker.x() - self.centerMarkerCharOffsetX) ) correctedC2C_y = self.getMD2BeamCenterY() + ( - y_click - (self.centerMarker.y() + self.centerMarkerCharOffsetY) + y_click - (self.centerMarker.y() - self.centerMarkerCharOffsetY) ) current_viewangle = daq_utils.mag1ViewAngle From b054da67ba4f137544a1d549c686c793c6695df5 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Mon, 6 Nov 2023 11:49:49 -0500 Subject: [PATCH 177/234] additional updates for raster motion --- daq_macros.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index f0e77364..c5ca651c 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3817,7 +3817,8 @@ def rasterDaq(rasterReqID): # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) - line_range = img_width_per_cell * numsteps + stepsize /= 1000 # MD2 wants mm + line_range = stepsize * numsteps total_uturn_range = line_range * number_of_lines start_cx = md2.cx.val() start_cy = md2.cy.val() From daa6d5a6bbd090f864c97a50715a2a113aba5267 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 9 Nov 2023 15:12:22 -0500 Subject: [PATCH 178/234] Testing fixes for the data loss. --- daq_macros.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index c5ca651c..91de153f 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3872,7 +3872,11 @@ def armed_callback(value, old_value, **kwargs): md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) + def armed_callback(value, old_value, **kwargs): + return (old_value == 1 and value == 0) + arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) yield from bp.fly([raster_flyer]) + arm_status.wait(timeout=5) From 0beca74e1f3566602330e7bd2b6946690f30d64b Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:22:22 -0500 Subject: [PATCH 179/234] Adding resolution drop down to file menu Final for now until testing --- gui/control_main.py | 40 +++++ gui/dialog/__init__.py | 2 + gui/dialog/resolution_calculator.py | 97 ++++++++++++ gui/dialog/resolution_dialog.py | 230 ++++++++++++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 gui/dialog/resolution_calculator.py create mode 100644 gui/dialog/resolution_dialog.py diff --git a/gui/control_main.py b/gui/control_main.py index 89606755..68e1ff45 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -57,6 +57,7 @@ SnapCommentDialog, StaffScreenDialog, UserScreenDialog, + CalculatorWindow ) from gui.raster import RasterCell, RasterGroup from gui.vector import VectorMarker, VectorWidget @@ -293,6 +294,9 @@ def parseXRFTable(self): XRFInfoDict[tokens[0]] = int(float(tokens[5]) * 100) XRFFile.close() return XRFInfoDict + + + def closeEvent(self, evnt): evnt.accept() @@ -2731,6 +2735,10 @@ def popBaseDirectoryDialogCB(self): ) if fname != "": self.dataPathGB.setBasePath_ledit(fname) + ''' + Creating function to for Actions under file drop down + + ''' def popImportDialogCB(self): #self.timerSample.stop() @@ -2750,6 +2758,14 @@ def setUserModeCB(self): def setExpertModeCB(self): self.vidActionDefineCenterRadio.setEnabled(True) + + + def openResolution(self): + self.sub = CalculatorWindow(self) + self.sub.show() + + + def upPriorityCB( self, @@ -4991,6 +5007,26 @@ def initUI(self): splitter1.addWidget(self.tabs) self.setCentralWidget(splitter1) splitterSizes = [600, 100] + + ''' + creating drop down menu items under File + + for now has + importAction - importing spreadsheet manually + @function - popImportDialogCB + + userAction - User Mode + @function - setUserModeCB + + expertAction - Expert Mode + @function - setExpertModeCB + + staffAction - Staff Panel + @function - popStaffDialogCB + + resolutionAction - Resolution Calculator + @function - openResolution + ''' importAction = QtWidgets.QAction("Import Spreadsheet...", self) importAction.triggered.connect(self.popImportDialogCB) modeGroup = QtWidgets.QActionGroup(self) @@ -5002,6 +5038,8 @@ def initUI(self): self.expertAction.triggered.connect(self.setExpertModeCB) self.staffAction = QtWidgets.QAction("Staff Panel...", self) self.staffAction.triggered.connect(self.popStaffDialogCB) + self.resolutionAction = QtWidgets.QAction("Resolution Calculator", self) + self.resolutionAction.triggered.connect(self.openResolution) modeGroup.addAction(self.userAction) modeGroup.addAction(self.expertAction) exitAction = QtWidgets.QAction(QtGui.QIcon("exit24.png"), "Exit", self) @@ -5011,6 +5049,7 @@ def initUI(self): self.statusBar() self.queue_collect_status_widget = QtWidgets.QLabel("Queue Collect: ON") self.statusBar().addPermanentWidget(self.queue_collect_status_widget) + menubar = self.menuBar() fileMenu = menubar.addMenu("&File") settingsMenu = menubar.addMenu("Settings") @@ -5018,6 +5057,7 @@ def initUI(self): fileMenu.addAction(self.userAction) fileMenu.addAction(self.expertAction) fileMenu.addAction(self.staffAction) + fileMenu.addAction(self.resolutionAction) # Define all of the available actions for the overlay color group self.BlueOverlayAction = QtWidgets.QAction("Blue", self, checkable=True) self.RedOverlayAction = QtWidgets.QAction("Red", self, checkable=True) diff --git a/gui/dialog/__init__.py b/gui/dialog/__init__.py index b50f1daa..177170f2 100644 --- a/gui/dialog/__init__.py +++ b/gui/dialog/__init__.py @@ -6,3 +6,5 @@ from .dewar import DewarDialog from .screen_defaults import ScreenDefaultsDialog from .set_energy import SetEnergyDialog +from .resolution_calculator import Calculator +from .resolution_dialog import CalculatorWindow diff --git a/gui/dialog/resolution_calculator.py b/gui/dialog/resolution_calculator.py new file mode 100644 index 00000000..394f9ab9 --- /dev/null +++ b/gui/dialog/resolution_calculator.py @@ -0,0 +1,97 @@ +import math +import sys + + +class Calculator: + """ + Make a calculator object that can calculate resolution formulas (and nothing else) + + """ + def __init__(self): + self.r = None + self.d = None + self.L = None + self.theta = None + self.wavelength = None + + def set_all_variables(self, variable_dict): + for key in variable_dict: + self.set_variables(key, variable_dict[key]) + + + + def set_variables(self, name, value): + if name == 'r': + self.r = value + elif name == 'd': + self.d = value + elif name == 'L': + self.L = value + elif name == 'theta': + self.theta = value + elif name == 'wavelength': + self.wavelength = value + + def calcD(self, r = None, L = None, wavelength = None, theta = None): + r = r or self.r + L = L or self.L + wavelength = wavelength or self.wavelength + theta = theta or self.theta + try: + denominator = 2*(math.sin((0.5*math.atan(r/L)) + theta)) + numerator = wavelength + return numerator/denominator + except Exception as e: + return e + + + + + def calcL(self, r = None, d = None, wavelength = None, theta = None): + r = r or self.r + d = d or self.d + wavelength = wavelength or self.wavelength + theta = theta or self.theta + try: + denominator = math.tan((2* math.asin(wavelength/(2*d) ) ) -(2*theta) ) + numerator = r + return numerator/denominator + except Exception as e: + return e + + + def calcTheta(self, r = None, L = None, wavelength = None, d = None): + r = r or self.r + L = L or self.L + wavelength = wavelength or self.wavelength + d = d or self.d + try: + val1 = math.asin(wavelength/(2*d)) + val2 = 0.5*math.atan(r/L) + return val1 - val2 + except Exception as e: + return e + + + def calcWavelength(self, r = None, L = None, d = None, theta = None): + r = r or self.r + L = L or self.L + d = d or self.d + theta = theta or self.theta + valin = 0.5*math.atan(r/L) + try: + wavelength = 2*d * math.sin(valin + theta) + return wavelength + except Exception as e: + return e + + + + + + + + + + + \ No newline at end of file diff --git a/gui/dialog/resolution_dialog.py b/gui/dialog/resolution_dialog.py new file mode 100644 index 00000000..f00d6445 --- /dev/null +++ b/gui/dialog/resolution_dialog.py @@ -0,0 +1,230 @@ +from qtpy.QtWidgets import * +from qtpy import QtCore +from qtpy import QtGui +from qtpy import QtWidgets +from qtpy.QtCore import * +from qtpy.QtGui import * +import sys +from gui.dialog import Calculator +import typing + +if typing.TYPE_CHECKING: + from lsdcGui import ControlMain + +WINDOW_SIZE = 480 + +#main qtpy window the calculator exists in + + +class CalculatorWindow(QtWidgets.QDialog): + def __init__(self, parent: "ControlMain"): + super(CalculatorWindow, self).__init__(parent) + self.setFixedSize(WINDOW_SIZE,WINDOW_SIZE) + #making radio buttons to choose formula + self.buttonDictionary = {'L': {'picker' : QRadioButton('Caluclate crystal to detector distance')}, + 'd': {'picker': QRadioButton("Calculate resolution")} , + 'theta': {'picker':QRadioButton("Calculate detector 2theta")}, + 'wavelength': {'picker':QRadioButton("Calculate wavelength")}, + 'r':{'value':None}} + + #making lines to hold inputs + self.r_value_enter = QComboBox() + self.r_value_enter.setToolTip("Detector Distance") + self.r_value_enter = QLineEdit() + self.r_value_enter.setPlaceholderText('Set r value (in mm)') + self.buttonDictionary['r']['value'] = self.r_value_enter + self.r_value_enter.setCurrentIndex(1) + #setting inputs to Double only + + self.L_value_enter = QLineEdit() + self.L_value_enter.setPlaceholderText('Set L value') + self.buttonDictionary['L']['value'] = self.L_value_enter + self.L_value_enter.setValidator(QDoubleValidator()) + + self.d_value_enter = QLineEdit() + self.d_value_enter.setPlaceholderText('Set d value') + self.buttonDictionary['d']['value'] = self.d_value_enter + self.d_value_enter.setValidator(QDoubleValidator()) + + self.theta_value_enter = QLineEdit() + self.theta_value_enter.setPlaceholderText('Set theta value') + self.buttonDictionary['theta']['value'] = self.theta_value_enter + self.theta_value_enter.setValidator(QDoubleValidator()) + + self.wave_value_enter = QLineEdit() + self.wave_value_enter.setPlaceholderText('Set wavelength value') + self.buttonDictionary['wavelength']['value'] = self.wave_value_enter + self.wave_value_enter.setValidator(QDoubleValidator()) + + + self.final_button = QPushButton('Calculate', self) + self.final_button.clicked.connect(self.calculateValue) + + self.bottom_text = QLabel() + self.bottom_text.setText('Enter values and Press button to calculate') + + + #creating calculator object + self.calculator = Calculator() + + + + layout = QVBoxLayout() + layout.addWidget(self.r_value_enter) + for key in self.buttonDictionary: + if 'picker' in self.buttonDictionary[key].keys(): + layout.addWidget(self.buttonDictionary[key]['picker']) + layout.addWidget(self.buttonDictionary[key]['value']) + layout.addWidget(self.final_button) + layout.addWidget(self.bottom_text) + self.setLayout(layout) + #self._createDisplay() + + + + # def _createButtons(self): + # buttonsLayout = QGridLayout() + # self.formula_picker = QRadioButton('Formula') + # self.b2 = QRadioButton("Button2") + + ''' + calls resolution calculator to calculate value depending on inputs from widgets + + -outputs + -value_to_return = value from formula calculated if no problems + -returns nothing if a problem occured, changes bottom_text + ''' + + def calculateValue(self): + checked_key = None + #checking which formula to use + for key in self.buttonDictionary: + if key != 'r' and self.buttonDictionary[key]['picker'].isChecked(): + checked_key = key + if checked_key == None: + self.bottom_text.setText("No calculation specified (press one of the radio buttons)") + return + + #getting values from textboxes r_value text box + r_value = self.r_value_enter.currentIndex() + convertValues = [200,244.7] + # print("r value = {}".format(r_value)) + #checking if value is a number string or empty string + r_value = convertValues[r_value] + r_value = float(r_value) + + + + d_value = self.d_value_enter.displayText() + #checking if value is string or none if not calculating that value (trying to use .isalpha but not when value is None) + if ((d_value == "" or d_value[0].isalpha() == True) and checked_key != 'd') : + self.bottom_text.setText("formula to calculate {} requires d value".format(checked_key)) + return + + l_value = self.L_value_enter.displayText() + if ((l_value == "" or l_value[0].isalpha() == True) and checked_key != 'L'): + self.bottom_text.setText("formula to calculate {} requires L value".format(checked_key)) + return + + theta_value = self.theta_value_enter.displayText() + if ((theta_value == "" or theta_value[0].isalpha() == True)and checked_key != 'theta'): + self.bottom_text.setText("formula to calculate {} requires theta value".format(checked_key)) + return + + wave_value = self.wave_value_enter.displayText() + if ((wave_value == "" or wave_value[0].isalpha() == True) and checked_key != 'wavelength'): + self.bottom_text.setText("formula to calculate {} requires the wavelenght".format(checked_key)) + return + + + #setting value to return if want value returned + value_to_return = None + + if checked_key == 'd': + l_value = float(self.L_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + + + + + variableDict = {'L':l_value, 'theta': theta_value, 'wavelength': wave_value, 'r': r_value} + + self.calculator.set_all_variables(variableDict) + d_value = self.calculator.calcD() + value_to_return = d_value + self.d_value_enter.setText(str(d_value)) + self.calculator.set_variables('d', d_value) + + + + + elif checked_key == 'L': + + D_value = float(self.D_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + + + + variableDict = {'d':d_value, 'theta': theta_value, 'wavelength': wave_value, 'r': r_value} + + self.calculator.set_all_variables(variableDict) + L_value = self.calculator.calcL() + value_to_return = L_value + self.L_value_enter.setText(str(L_value)) + self.calculator.set_variables('L', L_value) + + elif checked_key == 'theta': + + l_value = float(self.L_value_enter.displayText()) + d_value = float(self.d_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + + variableDict = {'L':l_value, 'd': d_value, 'wavelength': wave_value, 'r': r_value} + + self.calculator.set_all_variables(variableDict) + theta_value = self.calculator.calcTheta() + value_to_return = theta_value + self.theta_value_enter.setText(str(theta_value)) + self.calculator.set_variables('theta', theta_value) + + + + elif checked_key == 'wavelength': + + l_value = float(self.L_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + d_value = float(self.d_value_enter.displayText()) + variableDict = {'L':l_value, 'd': d_value, 'theta': theta_value, 'r': r_value} + + self.calculator.set_all_variables(variableDict) + wave_value = self.calculator.calcWavelength() + self.calculator.set_variables('wavelength', wave_value) + value_to_return = wave_value + self.wave_value_enter.setText(str(wave_value)) + + + + + + + + + self.bottom_text.setText("- Done Calculating - \n {} value = {}".format(checked_key, value_to_return)) + return value_to_return + + + + + + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = CalculatorWindow() + window.show() + + app.exec() \ No newline at end of file From bdd82430764947cb69ae27aaa2bfb694a94f41bb Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:23:25 -0500 Subject: [PATCH 180/234] changing r value edits --- gui/dialog/resolution_dialog.py | 35 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/gui/dialog/resolution_dialog.py b/gui/dialog/resolution_dialog.py index f00d6445..88c6c8d8 100644 --- a/gui/dialog/resolution_dialog.py +++ b/gui/dialog/resolution_dialog.py @@ -28,12 +28,18 @@ def __init__(self, parent: "ControlMain"): 'r':{'value':None}} #making lines to hold inputs - self.r_value_enter = QComboBox() - self.r_value_enter.setToolTip("Detector Distance") + # self.r_value_enter = QComboBox() + # self.r_value_enter.setToolTip("Detector Distance") + # self.r_value_enter = QLineEdit() + # self.r_value_enter.setPlaceholderText('Set r value (in mm)') + # self.buttonDictionary['r']['value'] = self.r_value_enter + # self.r_value_enter.setCurrentIndex(1) + + self.r_value_enter = QLineEdit() - self.r_value_enter.setPlaceholderText('Set r value (in mm)') + self.r_value_enter.setPlaceholderText('Set r value') self.buttonDictionary['r']['value'] = self.r_value_enter - self.r_value_enter.setCurrentIndex(1) + self.r_value_enter.setValidator(QDoubleValidator()) #setting inputs to Double only self.L_value_enter = QLineEdit() @@ -106,11 +112,22 @@ def calculateValue(self): return #getting values from textboxes r_value text box - r_value = self.r_value_enter.currentIndex() - convertValues = [200,244.7] - # print("r value = {}".format(r_value)) - #checking if value is a number string or empty string - r_value = convertValues[r_value] + # r_value = self.r_value_enter.currentIndex() + # convertValues = [200,244.7] + # # print("r value = {}".format(r_value)) + # #checking if value is a number string or empty string + # r_value = convertValues[r_value] + # r_value = float(r_value) + + r_value = self.r_value_enter.displayText() + # checking if value is a number string or empty string + if r_value == "" or r_value[0].isalpha() == True: + self.bottom_text.setText("formula to calculate {} requires r value".format(checked_key)) + return + elif float(r_value) < 140 or float(r_value) > 350: + self.bottom_text.setText("r value must be between 140 and 350") + return + r_value = float(r_value) From 2dffccc9a15489f7d684940c4699e44022d9c6d2 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 20 Nov 2023 19:24:03 -0500 Subject: [PATCH 181/234] updating error --- gui/dialog/resolution_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/dialog/resolution_dialog.py b/gui/dialog/resolution_dialog.py index 88c6c8d8..f1fab0af 100644 --- a/gui/dialog/resolution_dialog.py +++ b/gui/dialog/resolution_dialog.py @@ -179,7 +179,7 @@ def calculateValue(self): elif checked_key == 'L': - D_value = float(self.D_value_enter.displayText()) + d_value = float(self.d_value_enter.displayText()) theta_value = float(self.theta_value_enter.displayText()) wave_value = float(self.wave_value_enter.displayText()) From 6a15330cb41ae1c9c378e8faccfa367f5c487b85 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 30 Nov 2023 15:28:11 -0500 Subject: [PATCH 182/234] [WIP] raster motion working --- daq_macros.py | 15 +++++++-------- devices.py | 12 ++++++++++++ md2_flyers.py | 13 ++++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 91de153f..02881fba 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1215,7 +1215,7 @@ def snakeRaster(rasterReqID,grain=""): yield from raster_plan_wrapped(rasterReqID) else: finalize_plan = finalize_wrapper(snakeRasterBluesky(rasterReqID,grain), bps.mv(raster_flyer.detector.cam.acquire, 0)) - yield from finalize_plan + yield from finalize_plan #RE(snakeRasterBluesky(rasterReqID,grain)) def snakeRasterNoTile(rasterReqID,grain=""): @@ -3809,7 +3809,7 @@ def rasterDaq(rasterReqID): parentReqObj = parentRequest["request_obj"] detDist = parentReqObj["detDist"] - rasterFilePrefix = dataFilePrefix + "_Raster" + rasterFilePrefix = dataFilePrefix # + "_Raster" logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exposure_per_image}, {img_width_per_cell}, {wavelength}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {total_num_images}, {rows}") #logger.info(f"req_obj: {reqObj}") @@ -3819,15 +3819,18 @@ def rasterDaq(rasterReqID): # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) stepsize /= 1000 # MD2 wants mm line_range = stepsize * numsteps - total_uturn_range = line_range * number_of_lines + total_uturn_range = stepsize * number_of_lines start_cx = md2.cx.val() start_cy = md2.cy.val() frames_per_line = numsteps total_exposure_time = exposure_per_image * total_num_images - invert_direction = True + invert_direction = False use_centring_table = True use_fast_mesh_scans = True omega_range = 0 + logger.info(f"TASK INFO: {md2.task_info.get()}") + logger.info(f"TASK INFO[6]: {md2.task_info.get()[6]=='1'}") + logger.info(f"TASK OUTPUT: {md2.task_output}") logger.info(f"omega_range = {omegaRad}") logger.info(f"line_range = {line_range}") logger.info(f"total_uturn_range = {total_uturn_range}") @@ -3872,11 +3875,7 @@ def armed_callback(value, old_value, **kwargs): md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) - def armed_callback(value, old_value, **kwargs): - return (old_value == 1 and value == 0) - arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) yield from bp.fly([raster_flyer]) - arm_status.wait(timeout=5) diff --git a/devices.py b/devices.py index 32ad69c1..6399266d 100644 --- a/devices.py +++ b/devices.py @@ -155,6 +155,18 @@ class MD2Device(GonioDevice): detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=int(os.environ['EXPORTER_PORT']), name='exporter') + task_info = Cpt(EpicsSignalRO, 'LastTaskInfo',name='task_info') + task_output = Cpt(EpicsSignalRO, 'LastTaskOutput', name='task_output') + + def task_complete(self): + return self.task_output == "true" + + def task_status(self): + def check_task(*, old_value, value, **kwargs): + "Return True when last task completes" + return (value[6] == '1') + return SubscriptionStatus(self.task_info, check_task) + def is_ready(self): return self.state.get() == 4 diff --git a/md2_flyers.py b/md2_flyers.py index b18251b6..33b343cb 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -89,10 +89,17 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im def complete(self): # monitor md2 status, wait for ready or timeout and then return - ready_status = self.md2.ready_status() + #ready_status = self.md2.ready_status() + + #logger.debug(f"TASK INFO[6]: {self.md2.task_info[6]}") + #logger.debug(f"TASK OUTPUT: {self.md2.task_output}") + task_status = self.md2.task_status() timeout = self.collection_params["exposure_time"] + 10 - ready_status.wait(timeout=timeout) - return ready_status + #ready_status.wait(timeout=timeout) + task_status.wait(timeout=timeout) + #logger.debug(f"TASK INFO: {self.md2.task_info}") + #logger.debug(f"TASK OUTPUT: {self.md2.task_output}") + return task_status def describe_collect(self): return {"stream_name": {}} From c6064bd4912f2fa3d06983ab56bd867ba0f45732 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Sat, 2 Dec 2023 02:34:24 -0500 Subject: [PATCH 183/234] raster moves to correct starting point --- daq_macros.py | 12 ++++++++---- md2_flyers.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 02881fba..fd7c2d59 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3813,14 +3813,18 @@ def rasterDaq(rasterReqID): logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exposure_per_image}, {img_width_per_cell}, {wavelength}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {total_num_images}, {rows}") #logger.info(f"req_obj: {reqObj}") - - # zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) + i = 0 + xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(rows[i], stepsize, (start_omega*0), start_x, start_y, start_z, i) # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) stepsize /= 1000 # MD2 wants mm + logger.info(f"move calculations: {xMotAbsoluteMove}, {xEnd}, {yMotAbsoluteMove}, {yEnd}, {zMotAbsoluteMove}, {zEnd}") line_range = stepsize * numsteps total_uturn_range = stepsize * number_of_lines - start_cx = md2.cx.val() + start_y = start_y - (xEnd / 1000) + start_z = start_z - (yMotAbsoluteMove / 1000) + #start_z = start_z - (xEnd / 1000) + start_cx = md2.cx.val()# + (xEnd/1000) start_cy = md2.cy.val() frames_per_line = numsteps total_exposure_time = exposure_per_image * total_num_images @@ -3836,6 +3840,7 @@ def rasterDaq(rasterReqID): logger.info(f"total_uturn_range = {total_uturn_range}") logger.info(f"start_omega = {start_omega}") logger.info(f"start_y = {start_y}") + logger.info(f"current yzcxcy: {md2.y.get()}, {md2.z.get()}, {md2.cx.get()}, {md2.cy.get()}") logger.info(f"start_z = {start_z}") logger.info(f"start_cx = {start_cx}") logger.info(f"start_cy = {start_cy}") @@ -3846,7 +3851,6 @@ def rasterDaq(rasterReqID): logger.info(f"use_centring_table = {use_centring_table}") logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") - if raster_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' diff --git a/md2_flyers.py b/md2_flyers.py index 33b343cb..3d0215ef 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -94,7 +94,7 @@ def complete(self): #logger.debug(f"TASK INFO[6]: {self.md2.task_info[6]}") #logger.debug(f"TASK OUTPUT: {self.md2.task_output}") task_status = self.md2.task_status() - timeout = self.collection_params["exposure_time"] + 10 + timeout = self.collection_params["exposure_time"] + 60 #ready_status.wait(timeout=timeout) task_status.wait(timeout=timeout) #logger.debug(f"TASK INFO: {self.md2.task_info}") From 5f53f0ccc7211e22293e7ba3c086e69a9c3cdada Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 12 Dec 2023 03:34:03 -0500 Subject: [PATCH 184/234] [DONOTMERGE] dozor testing progress --- daq_macros.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index fd7c2d59..fd0f78ff 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1003,7 +1003,8 @@ def runDozorThread(directory, time.sleep(1.0) #allow for file writing - node = getNodeName("spot", rowIndex, 8) + #node = getNodeName("spot", rowIndex, 8) + node = "titania-cpu001" if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, @@ -3880,6 +3881,24 @@ def armed_callback(value, old_value, **kwargs): logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) yield from bp.fly([raster_flyer]) + spotFindThreadList = [] + row_index = 1 + logger.info(f"raster prefix {rasterFilePrefix}") + rasterFilePrefix = rasterFilePrefix.split("/")[-1] + logger.info(f"raster prefix {rasterFilePrefix}") + for i in range(1, number_of_lines-1): + row_index = i + seqNum = raster_flyer.detector.cam.sequence_id.get() + spotFindThread = Thread(target=runDozorThread,args=(data_directory_name, #TODO this can't move outside of the thread checking block + rasterFilePrefix, + row_index, + numsteps, + seqNum, + reqObj, + rasterReqID)) + spotFindThread.start() + spotFindThreadList.append(spotFindThread) + @@ -3893,6 +3912,7 @@ def clean_up_collection(): gov_status.wait(timeout=30) yield from bps.mv(md2.phase, 0) md2.ready_status().wait(timeout= 20) + # trigger processing here logger.info(f"clean_up took {time.time()-start_time} seconds.") def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): From 6cc94e5f42ef9a280215850f6e7893d43dbd7490 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 23 Jan 2024 14:26:01 -0500 Subject: [PATCH 185/234] Logger changes while testing --- md2_flyers.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 3d0215ef..5729b48d 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -36,7 +36,7 @@ def kickoff(self): start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["scan_range"], exposure_time=self.collection_params["exposure_time"]) - logger.info(f"md2_msg: {md2_msg}") + logger.info(f"md2 KICKOFF msg: {md2_msg}") return NullStatus() def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): @@ -90,15 +90,18 @@ def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_im def complete(self): # monitor md2 status, wait for ready or timeout and then return #ready_status = self.md2.ready_status() - - #logger.debug(f"TASK INFO[6]: {self.md2.task_info[6]}") - #logger.debug(f"TASK OUTPUT: {self.md2.task_output}") + + #logger.info(f"TASK INFO[6]: {self.md2.task_info[6]}") + #logger.info(f"TASK OUTPUT: {self.md2.task_output}") + logger.info(f"FLYER COMPLETE FUNCTION") task_status = self.md2.task_status() - timeout = self.collection_params["exposure_time"] + 60 + logger.info(f"assigning task status") + timeout = self.collection_params["exposure_time"] + 40 + logger.info(f"TASK TIMEOUT: {timeout}") #ready_status.wait(timeout=timeout) task_status.wait(timeout=timeout) - #logger.debug(f"TASK INFO: {self.md2.task_info}") - #logger.debug(f"TASK OUTPUT: {self.md2.task_output}") + logger.info(f"TASK INFO: {self.md2.task_info}") + logger.info(f"TASK OUTPUT: {self.md2.task_output}") return task_status def describe_collect(self): @@ -206,7 +209,7 @@ def kickoff(self): stop_cy=self.collection_params["stop_cy"], stop_y=self.collection_params["stop_y"], stop_z=self.collection_params["stop_z"],) - logger.info(f"md2_msg: {md2_msg}") + logger.info(f"md2 VEC KICKOFF msg: {md2_msg}") return NullStatus() def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy): @@ -254,7 +257,7 @@ def kickoff(self): invert_direction=self.collection_params["invert_direction"], use_centring_table=self.collection_params["use_centring_table"], use_fast_mesh_scans=self.collection_params["use_fast_mesh_scans"]) - logger.info(f"md2_msg: {md2_msg}") + logger.info(f"md2 RASTER KICKOFF msg: {md2_msg}") return NullStatus() def update_parameters(self, omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans): From 2d9ed213fdfe7394563c1d59a8f52dc5402ae61e Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 1 Feb 2024 14:39:27 -0500 Subject: [PATCH 186/234] adjustments to c2c and rudra's dewar update --- config_params.py | 4 ++-- gui/control_main.py | 4 ++-- gui/dialog/dewar.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config_params.py b/config_params.py index 95150c41..95d58a6f 100644 --- a/config_params.py +++ b/config_params.py @@ -88,8 +88,8 @@ class OnMountAvailOptions(Enum): DETECTOR_SAFE_DISTANCE = {"fmx": 200.0, "amx": 180.0, "nyx": 200.0} GOVERNOR_TIMEOUT = 120 # seconds for a governor move -DEWAR_SECTORS = {"amx": 8, "fmx": 8, "nyx": 5} -PUCKS_PER_DEWAR_SECTOR = {"amx": 3, "fmx": 3, "nyx": 3} +DEWAR_SECTORS = {'amx':8, 'fmx':8, 'nyx':8} +PUCKS_PER_DEWAR_SECTOR = {'amx':3, 'fmx':3, 'nyx':3} cryostreamTempPV = {"amx": "XF:17IDB-ES:AMX{CS:1}SAMPLE_TEMP_RBV", "fmx": "FMX:cs700:gasT-I"} diff --git a/gui/control_main.py b/gui/control_main.py index 68e1ff45..4abfb739 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3815,8 +3815,8 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - correctedC2C_x = x_click + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) - correctedC2C_y = y_click + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) + correctedC2C_x = x_click + 25 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) + correctedC2C_y = y_click + 5 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) lsdc_x = daq_utils.screenPixX lsdc_y = daq_utils.screenPixY md2_x = self.md2.center_pixel_x.get() * 2 diff --git a/gui/dialog/dewar.py b/gui/dialog/dewar.py index b3ae55cd..fb510540 100644 --- a/gui/dialog/dewar.py +++ b/gui/dialog/dewar.py @@ -59,7 +59,8 @@ def initUI(self): for j in range(0, self.pucksPerDewarSector): dataIndex = (i * self.pucksPerDewarSector) + j self.allButtonList[dataIndex] = QtWidgets.QPushButton( - (str(self.data[dataIndex])) + #(str(self.data[dataIndex])) + '{}: {}'.format(str(dataIndex+1),str(self.data[dataIndex])) ) self.allButtonList[dataIndex].clicked.connect( functools.partial(self.on_button, str(dataIndex)) From 98639ccd961cfe688ff6ff486644667062b88cb5 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 28 Feb 2024 13:39:06 -0500 Subject: [PATCH 187/234] Raster processing and dozor display --- daq_macros.py | 35 ++++++++++++++++++++++++++++++++--- gui/control_main.py | 8 ++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index fd0f78ff..39d8181f 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1001,10 +1001,10 @@ def runDozorThread(directory, """ global rasterRowResultsList,processedRasterRowCount - time.sleep(1.0) #allow for file writing + time.sleep(10.0) #allow for file writing #node = getNodeName("spot", rowIndex, 8) - node = "titania-cpu001" + node = "titania-cpu002" if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, @@ -3663,6 +3663,17 @@ def vector_plan_wrapped(currentRequest): def raster_plan_wrapped(rasterReqID): yield from finalize_wrapper(rasterDaq(rasterReqID), clean_up_collection()) + #time.sleep(15) + #rasterRequest = db_lib.getRequestByID(rasterReqID) + #rasterResult = generateGridMap(rasterRequest) + #rasterRequest["request_obj"]["rasterDef"]["status"] = ( + # RasterStatus.READY_FOR_SNAPSHOT.value + #) + #db_lib.updateRequest(rasterRequest) + #db_lib.updatePriority(rasterReqID,-1) + #daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) + + def standardDaq(currentRequest): # collect all parameters @@ -3886,8 +3897,9 @@ def armed_callback(value, old_value, **kwargs): logger.info(f"raster prefix {rasterFilePrefix}") rasterFilePrefix = rasterFilePrefix.split("/")[-1] logger.info(f"raster prefix {rasterFilePrefix}") - for i in range(1, number_of_lines-1): + for i in range(0, number_of_lines): row_index = i + logger.info(f'spot finding for row {i}') seqNum = raster_flyer.detector.cam.sequence_id.get() spotFindThread = Thread(target=runDozorThread,args=(data_directory_name, #TODO this can't move outside of the thread checking block rasterFilePrefix, @@ -3898,6 +3910,23 @@ def armed_callback(value, old_value, **kwargs): rasterReqID)) spotFindThread.start() spotFindThreadList.append(spotFindThread) + [thread.join(timeout=120) for thread in spotFindThreadList] + logger.info(str(processedRasterRowCount) + "/" + str(number_of_lines)) + rasterResult = generateGridMap(rasterRequest) + rasterRequestID = rasterRequest["uid"] + rasterRequest["request_obj"]["rasterDef"]["status"] = ( + RasterStatus.READY_FOR_SNAPSHOT.value + ) + db_lib.updateRequest(rasterRequest) + db_lib.updatePriority(rasterRequestID,-1) + if (rasterRequest["request_obj"]["rasterDef"]["numCells"] + > getBlConfig(RASTER_NUM_CELLS_DELAY_THRESHOLD)): + #larger rasters can delay GUI scene update + time.sleep(getBlConfig(RASTER_LONG_SNAPSHOT_DELAY)) + else: + time.sleep(getBlConfig(RASTER_SHORT_SNAPSHOT_DELAY)) + daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) + diff --git a/gui/control_main.py b/gui/control_main.py index 4abfb739..dd32423e 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3798,10 +3798,10 @@ def pixelSelect(self, event): return fov = self.getCurrentFOV() correctedC2C_x = self.getMD2BeamCenterX() + ( - x_click - (self.centerMarker.x() - self.centerMarkerCharOffsetX) + x_click - (self.centerMarker.x() - self.centerMarkerCharOffsetX) - 20 ) correctedC2C_y = self.getMD2BeamCenterY() + ( - y_click - (self.centerMarker.y() - self.centerMarkerCharOffsetY) + y_click - (self.centerMarker.y() - self.centerMarkerCharOffsetY) - 40 ) current_viewangle = daq_utils.mag1ViewAngle @@ -3815,8 +3815,8 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 if daq_utils.exporter_enabled: - correctedC2C_x = x_click + 25 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) - correctedC2C_y = y_click + 5 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) + correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) + correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) lsdc_x = daq_utils.screenPixX lsdc_y = daq_utils.screenPixY md2_x = self.md2.center_pixel_x.get() * 2 From 38f91c4519ba2dfe480cb93db8cfd8932c4d7e63 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:05:35 -0500 Subject: [PATCH 188/234] replacing beam available with three click signal variable --- gui/control_main.py | 56 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index dd32423e..ed3d69c4 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -145,7 +145,7 @@ class ControlMain(QtWidgets.QMainWindow): fastShutterSignal = QtCore.Signal(float) gripTempSignal = QtCore.Signal(float) ringCurrentSignal = QtCore.Signal(float) - beamAvailableSignal = QtCore.Signal(float) + threeClickSignal = QtCore.Signal(float) sampleExposedSignal = QtCore.Signal(float) sampMoveSignal = QtCore.Signal(int, str) roiChangeSignal = QtCore.Signal(int, str) @@ -1491,6 +1491,15 @@ def createSampleTab(self): ringCurrentMessageLabel = QtWidgets.QLabel("Ring (mA):") self.ringCurrentMessage = QtWidgets.QLabel(str(self.ringCurrent_pv.get())) beamAvailable = self.beamAvailable_pv.get() + + ''' + changing beam available label + + + + ''' + + if beamAvailable: self.beamAvailLabel = QtWidgets.QLabel("Beam Available") self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") @@ -1517,6 +1526,13 @@ def createSampleTab(self): else: self.cryostreamTempLabel = QtWidgets.QLabel("N/A") + + + + ''' + Adding bottom labels to gui + + ''' fileHBoxLayout.addWidget(gripperLabel) fileHBoxLayout.addWidget(self.gripperTempLabel) fileHBoxLayout.addWidget(cryostreamLabel) @@ -2201,6 +2217,12 @@ def processMountedPin(self, mountedPinPos): self.eraseCB() self.treeChanged_pv.put(1) + + ''' + functions to bottom status variables + + ''' + def processFastShutter(self, shutterVal): if round(shutterVal) == round(self.fastShutterOpenPos_pv.get()): self.shutterStateLabel.setText("Shutter State:Open") @@ -2233,13 +2255,18 @@ def processRingCurrent(self, ringCurrentVal): else: self.ringCurrentMessage.setStyleSheet("background-color: #99FF66;") - def processBeamAvailable(self, beamAvailVal): - if int(beamAvailVal) == 1: + ''' + change beam abailable set text depending on input + + function is processThreClickCentering + ''' + def processThreeClickCentering(self, beamAvailVal): + if int(beamAvailVal) == 0: self.beamAvailLabel.setText("Beam Available") self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") else: - self.beamAvailLabel.setText("No Beam") - self.beamAvailLabel.setStyleSheet("background-color: red;") + self.beamAvailLabel.setText(beamAvailVal) + self.beamAvailLabel.setStyleSheet("background-color: yellow") def processSampleExposed(self, sampleExposedVal): if int(sampleExposedVal) == 1: @@ -3094,6 +3121,7 @@ def measurePolyCB(self): def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 + self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): self.md2.exporter.cmd("startManualSampleCentring", "") @@ -3760,7 +3788,11 @@ def sceneKey(self, event): self.centeringMarksList[i]["graphicsItem"] ) self.centeringMarksList[i] = None + ''' + When picking pixels + + ''' def pixelSelect(self, event): super(QtWidgets.QGraphicsPixmapItem, self.pixmap_item).mousePressEvent(event) x_click = float(event.pos().x()) @@ -3811,9 +3843,13 @@ def pixelSelect(self, event): current_viewangle = daq_utils.mag3ViewAngle elif self.zoom4Radio.isChecked(): current_viewangle = daq_utils.mag4ViewAngle - + ''' + Three click centering will update self.threeClickSignal.emit(self.threeClickCount) + + ''' if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 + self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) if daq_utils.exporter_enabled: correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) @@ -3828,6 +3864,7 @@ def pixelSelect(self, event): self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") if self.threeClickCount == 4: self.threeClickCount = 0 + self.threeClickSignal.emit(0) self.click3Button.setStyleSheet("background-color: None") return else: @@ -3856,6 +3893,7 @@ def pixelSelect(self, event): self.aux_send_to_server(*comm_s) if self.threeClickCount == 4: self.threeClickCount = 0 + self.threeClickSignal.emit(0) self.click3Button.setStyleSheet("background-color: None") return @@ -4933,8 +4971,8 @@ def ringCurrentChangedCB(self, value=None, char_value=None, **kw): self.ringCurrentSignal.emit(ringCurrentVal) def beamAvailableChangedCB(self, value=None, char_value=None, **kw): - beamAvailableVal = value - self.beamAvailableSignal.emit(beamAvailableVal) + threeClickVal = value + self.threeClickSignal.emit(threeClickVal) def sampleExposedChangedCB(self, value=None, char_value=None, **kw): sampleExposedVal = value @@ -5283,7 +5321,7 @@ def initCallbacks(self): self.cryostreamTemp_pv.add_callback(self.cryostreamTempChangedCB) self.ringCurrentSignal.connect(self.processRingCurrent) self.ringCurrent_pv.add_callback(self.ringCurrentChangedCB) - self.beamAvailableSignal.connect(self.processBeamAvailable) + self.threeClickSignal.connect(self.processThreeClickCentering) self.beamAvailable_pv.add_callback(self.beamAvailableChangedCB) self.sampleExposedSignal.connect(self.processSampleExposed) self.sampleExposed_pv.add_callback(self.sampleExposedChangedCB) From 891a582c3741e91b9a333eed45afe088e4cb3e7f Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:47:17 -0500 Subject: [PATCH 189/234] Update control_main.py --- gui/control_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index ed3d69c4..2c352dc5 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -145,7 +145,7 @@ class ControlMain(QtWidgets.QMainWindow): fastShutterSignal = QtCore.Signal(float) gripTempSignal = QtCore.Signal(float) ringCurrentSignal = QtCore.Signal(float) - threeClickSignal = QtCore.Signal(float) + threeClickSignal = QtCore.Signal(str) sampleExposedSignal = QtCore.Signal(float) sampMoveSignal = QtCore.Signal(int, str) roiChangeSignal = QtCore.Signal(int, str) From 075c546c09fadafdac2aa7651f49705b3b5143a7 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:55:04 -0500 Subject: [PATCH 190/234] Update control_main.py --- gui/control_main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 2c352dc5..ae754cb8 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -2261,7 +2261,7 @@ def processRingCurrent(self, ringCurrentVal): function is processThreClickCentering ''' def processThreeClickCentering(self, beamAvailVal): - if int(beamAvailVal) == 0: + if beamAvailVal == '0': self.beamAvailLabel.setText("Beam Available") self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") else: @@ -3864,7 +3864,7 @@ def pixelSelect(self, event): self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") if self.threeClickCount == 4: self.threeClickCount = 0 - self.threeClickSignal.emit(0) + self.threeClickSignal.emit('0') self.click3Button.setStyleSheet("background-color: None") return else: @@ -3893,7 +3893,7 @@ def pixelSelect(self, event): self.aux_send_to_server(*comm_s) if self.threeClickCount == 4: self.threeClickCount = 0 - self.threeClickSignal.emit(0) + self.threeClickSignal.emit('0') self.click3Button.setStyleSheet("background-color: None") return From 68f0f262556f163cda4bf810bb64e83deafe0f66 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 20 Nov 2023 23:26:35 -0500 Subject: [PATCH 191/234] adding sleep time --- gui/control_main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index ae754cb8..2d614d25 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3122,6 +3122,7 @@ def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) + time.sleep(0.02) self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): self.md2.exporter.cmd("startManualSampleCentring", "") @@ -3850,6 +3851,7 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) + time.sleep(0.02) if daq_utils.exporter_enabled: correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) From 8a0e8b5d2c33bb8de509c9693098e98a10fcaf0e Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 20 Nov 2023 23:32:18 -0500 Subject: [PATCH 192/234] longer sleep time and adding print statement --- gui/control_main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 2d614d25..c2830474 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3122,7 +3122,7 @@ def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) - time.sleep(0.02) + time.sleep(0.3) self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): self.md2.exporter.cmd("startManualSampleCentring", "") @@ -3851,7 +3851,8 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) - time.sleep(0.02) + time.sleep(0.3) + print('sleeping for 0.3 seconds') if daq_utils.exporter_enabled: correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) From 34df45d3ff68b3ab11fda67184028223375205e1 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:39:08 -0500 Subject: [PATCH 193/234] editing Control_main to call exporter command and wait in three click centering --- gui/control_main.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index c2830474..a013bb87 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3851,8 +3851,8 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) - time.sleep(0.3) - print('sleeping for 0.3 seconds') + + if daq_utils.exporter_enabled: correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) @@ -3865,6 +3865,10 @@ def pixelSelect(self, event): correctedC2C_x = correctedC2C_x * scale_x correctedC2C_y = correctedC2C_y * scale_y self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") + logger.info('waiting for motor rotation') + time.sleep(0.2) + self.omegaMoveCheck(0.02,'OmegaState') + if self.threeClickCount == 4: self.threeClickCount = 0 self.threeClickSignal.emit('0') @@ -3898,8 +3902,27 @@ def pixelSelect(self, event): self.threeClickCount = 0 self.threeClickSignal.emit('0') self.click3Button.setStyleSheet("background-color: None") + return + ''' + Function to check if MD motors are rotating or not + ''' + def omegaMoveCheck(self, sleeptime,call='OmegaState'): + state = self.md2.exporter.read(call) + while(state == 'Moving'): + time.sleep(sleeptime) + state = self.md2.exporter.read(call) + logger.info('\nIn Moving\n{}\n'.format(state)) + if state == 'Ready': + logger.info('ready for next click') + return state + else: + logger.info('\ndone moving, current state is: {}'.format(state)) + return state + + + def editScreenParamsCB(self): self.screenDefaultsDialog = ScreenDefaultsDialog(self) self.screenDefaultsDialog.show() From a2edec85d95835cfeccd0be56ed658eef647e664 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:43:53 -0500 Subject: [PATCH 194/234] Making click not register until motor is ready --- gui/control_main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index a013bb87..9586f291 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3800,6 +3800,16 @@ def pixelSelect(self, event): y_click = float(event.pos().y()) penGreen = QtGui.QPen(QtCore.Qt.green) penRed = QtGui.QPen(QtCore.Qt.red) + ''' + For three click centering, this if statement checks the omega state of the motor. + This ideally gives feedback on wether the MD2 is in the rotation portion of the three click centering + + ''' + state = self.md2.exporter.read('OmegaState') + if state != 'Ready': + logger.info('waiting for motor rotation') + logger.info('Click not registered') + return if self.vidActionDefineCenterRadio.isChecked(): self.vidActionC2CRadio.setChecked( True @@ -3865,9 +3875,9 @@ def pixelSelect(self, event): correctedC2C_x = correctedC2C_x * scale_x correctedC2C_y = correctedC2C_y * scale_y self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") - logger.info('waiting for motor rotation') - time.sleep(0.2) - self.omegaMoveCheck(0.02,'OmegaState') + #logger.info('waiting for motor rotation') + #time.sleep(0.2) + #self.omegaMoveCheck(0.02,'OmegaState') if self.threeClickCount == 4: self.threeClickCount = 0 @@ -3913,7 +3923,7 @@ def omegaMoveCheck(self, sleeptime,call='OmegaState'): while(state == 'Moving'): time.sleep(sleeptime) state = self.md2.exporter.read(call) - logger.info('\nIn Moving\n{}\n'.format(state)) + #logger.info('\nIn Moving\n{}\n'.format(state)) if state == 'Ready': logger.info('ready for next click') return state From 1628dfe77ee0711693700ded4bf197d6b05bd1d1 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:02:21 -0500 Subject: [PATCH 195/234] adding line drawing to three click centering --- gui/control_main.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 9586f291..da18d59d 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1076,6 +1076,7 @@ def createSampleTab(self): self.rasterList = [] self.rasterDefList = [] self.polyPointItems = [] + self.threeClickLines = [] self.rasterPoly = None self.measureLine = None self.scene = QtWidgets.QGraphicsScene(0, 0, 640, 512, self) @@ -3122,7 +3123,7 @@ def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) - time.sleep(0.3) + #time.sleep(0.3) self.click3Button.setStyleSheet("background-color: yellow") if(daq_utils.exporter_enabled): self.md2.exporter.cmd("startManualSampleCentring", "") @@ -3861,6 +3862,11 @@ def pixelSelect(self, event): if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) + #adding drawing for three click centering + logger.info('Drawing 3 click line {} at x_value: {} and y_value {}'.format(self.threeClickCount, x_click, y_click)) + self.threeClickLines.append( + self.scene.addLine(x_click, 0, x_click, 512, penGreen) + ) if daq_utils.exporter_enabled: @@ -3883,6 +3889,11 @@ def pixelSelect(self, event): self.threeClickCount = 0 self.threeClickSignal.emit('0') self.click3Button.setStyleSheet("background-color: None") + #removing drawing for three click centering + logger.info('Removing 3 click lines') + for i in range(len(self.threeClickLines)): + self.scene.removeItem(self.threeClickLines[i]) + self.threeClickLines = [] return else: comm_s = ( @@ -3912,6 +3923,12 @@ def pixelSelect(self, event): self.threeClickCount = 0 self.threeClickSignal.emit('0') self.click3Button.setStyleSheet("background-color: None") + #removing drawing for three cick centering + logger.info('Removing 3 click lines') + for i in range(len(self.threeClickLines)): + self.scene.removeItem(self.threeClickLines[i]) + logger.info('Removed line {}'.format(i)) + self.threeClickLines = [] return From d569ebcf7e7de252aacfc18444dc07ac1131a290 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 4 Mar 2024 13:20:49 -0500 Subject: [PATCH 196/234] beamstop distance set to 20 before each collection --- daq_macros.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index 39d8181f..1fefeb00 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3703,6 +3703,8 @@ def standardDaq(currentRequest): angle_start = sweep_start_angle wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + yield from bps.mv(beamstop.distance, 20.0) + if flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' @@ -3775,6 +3777,8 @@ def vectorDaq(currentRequest): stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] + yield from bps.mv(beamstop.distance, 20.0) + if vector_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' @@ -3863,6 +3867,8 @@ def rasterDaq(rasterReqID): logger.info(f"use_centring_table = {use_centring_table}") logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") + yield from bps.mv(beamstop.distance, 20.0) + if raster_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' From a31ecc5f7d1fcc5b9c8153d28a326c5384bbc709 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 4 Mar 2024 13:34:06 -0500 Subject: [PATCH 197/234] switched to 4D standard scans --- md2_flyers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/md2_flyers.py b/md2_flyers.py index 5729b48d..d3cb1da3 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -32,8 +32,7 @@ def __init__(self, md2, detector=None) -> None: self._collection_dictionary = None def kickoff(self): - md2_msg = self.md2.standard_scan(num_images=self.collection_params["total_num_images"], - start_angle=self.collection_params["start_angle"], + md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], scan_range=self.collection_params["scan_range"], exposure_time=self.collection_params["exposure_time"]) logger.info(f"md2 KICKOFF msg: {md2_msg}") From b72714ebba2161c9b47c51bcb6c37403eadfbe6a Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 4 Mar 2024 14:47:12 -0500 Subject: [PATCH 198/234] Added new device position preset for beamstop --- daq_macros.py | 6 +++--- devices.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 1fefeb00..e136e86a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3703,7 +3703,7 @@ def standardDaq(currentRequest): angle_start = sweep_start_angle wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - yield from bps.mv(beamstop.distance, 20.0) + yield from bps.mv(beamstop.distance_preset, 20.0) if flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' @@ -3777,7 +3777,7 @@ def vectorDaq(currentRequest): stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] - yield from bps.mv(beamstop.distance, 20.0) + yield from bps.mv(beamstop.distance_preset, 20.0) if vector_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' @@ -3867,7 +3867,7 @@ def rasterDaq(rasterReqID): logger.info(f"use_centring_table = {use_centring_table}") logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") - yield from bps.mv(beamstop.distance, 20.0) + yield from bps.mv(beamstop.distance_preset, 20.0) if raster_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' diff --git a/devices.py b/devices.py index 6399266d..93a8ca48 100644 --- a/devices.py +++ b/devices.py @@ -117,6 +117,7 @@ def set_level(self, level): class BeamstopDevice(Device): distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") + distance_preset = Cpt(EpicsSignal, "BeamstopDistance", name="distance_preset") x = Cpt(MD2Positioner, "BeamstopX", name="x") y = Cpt(MD2Positioner, "BeamstopY", name="y") z = Cpt(MD2Positioner, "BeamstopZ", name="z") From 84f3a8180e458ba287eb7c69161899bbde55005f Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 18 Mar 2024 12:15:42 -0400 Subject: [PATCH 199/234] standard collections now save centring prior to phase transition --- daq_macros.py | 4 +++- devices.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index e136e86a..5c07fb47 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3704,7 +3704,7 @@ def standardDaq(currentRequest): wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) yield from bps.mv(beamstop.distance_preset, 20.0) - + md2.save_center() if flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' @@ -3777,6 +3777,7 @@ def vectorDaq(currentRequest): stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] + md2.save_center() yield from bps.mv(beamstop.distance_preset, 20.0) if vector_flyer.detector.cam.armed.get() == 1: @@ -3867,6 +3868,7 @@ def rasterDaq(rasterReqID): logger.info(f"use_centring_table = {use_centring_table}") logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") + md2.save_center() yield from bps.mv(beamstop.distance_preset, 20.0) if raster_flyer.detector.cam.armed.get() == 1: diff --git a/devices.py b/devices.py index 93a8ca48..a751d6d1 100644 --- a/devices.py +++ b/devices.py @@ -149,6 +149,7 @@ class MD2Device(GonioDevice): center_pixel_x = Cpt(EpicsSignalRO, 'BeamPositionHorizontal',name='center_pixel_x') center_pixel_y = Cpt(EpicsSignalRO, 'BeamPositionVertical',name='center_pixel_y') centring_click = Cpt(EpicsSignal, 'setCentringClick',name='centring_click') + centring_save = Cpt(EpicsSignal, 'saveCentringPositions', name='centring_save') state = Cpt(EpicsSignalRO, 'State',name='state') phase = Cpt(EpicsSignal, 'CurrentPhase',name='phase') phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') @@ -159,6 +160,10 @@ class MD2Device(GonioDevice): task_info = Cpt(EpicsSignalRO, 'LastTaskInfo',name='task_info') task_output = Cpt(EpicsSignalRO, 'LastTaskOutput', name='task_output') + + def save_center(self): + self.centring_save.put("__EMPTY__") + def task_complete(self): return self.task_output == "true" From aa8860ee6aefc8863366c7c1e7bf64fa40cce7da Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 5 Mar 2024 16:26:27 -0500 Subject: [PATCH 200/234] standard scan full vector conversion --- daq_macros.py | 11 ++++++++++- md2_flyers.py | 28 ++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 5c07fb47..7efa180c 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3703,6 +3703,15 @@ def standardDaq(currentRequest): angle_start = sweep_start_angle wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) + start_y = md2.y.val() + start_z = md2.z.val() + start_cx = md2.cx.val() + start_cy = md2.cy.val() + stop_cx = md2.cx.val() + stop_cy = md2.cy.val() + stop_y = md2.y.val() + stop_z = md2.z.val() + yield from bps.mv(beamstop.distance_preset, 20.0) md2.save_center() if flyer.detector.cam.armed.get() == 1: @@ -3734,7 +3743,7 @@ def armed_callback(value, old_value, **kwargs): yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) + flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) logger.info(f"flyer handoff") yield from bp.fly([flyer]) logger.info(f"fly complete") diff --git a/md2_flyers.py b/md2_flyers.py index d3cb1da3..f945c5e2 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -32,20 +32,36 @@ def __init__(self, md2, detector=None) -> None: self._collection_dictionary = None def kickoff(self): + # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], - scan_range=self.collection_params["scan_range"], - exposure_time=self.collection_params["exposure_time"]) - logger.info(f"md2 KICKOFF msg: {md2_msg}") + scan_range=self.collection_params["scan_range"], + exposure_time=self.collection_params["exposure_time"], + start_cx=self.collection_params["start_cx"], + start_cy=self.collection_params["start_cy"], + start_y=self.collection_params["start_y"], + start_z=self.collection_params["start_z"], + stop_cx=self.collection_params["stop_cx"], + stop_cy=self.collection_params["stop_cy"], + stop_y=self.collection_params["stop_y"], + stop_z=self.collection_params["stop_z"],) + logger.info(f"md2 VEC KICKOFF msg: {md2_msg}") return NullStatus() - def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): + def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy): self.collection_params = { - "total_num_images": total_num_images, "start_angle": start_angle, "scan_range": scan_range, "exposure_time": exposure_time, + "start_cx": start_cx, + "start_cy": start_cy, + "start_y": start_y, + "start_z": start_z, + "stop_cx": stop_cx, + "stop_cy": stop_cy, + "stop_y": stop_y, + "stop_z": stop_z, } - + def configure_detector(self, file_prefix, data_directory_name): self.detector.file.external_name.put(file_prefix) self.detector.file.write_path_template = data_directory_name From 614b20273d4eda36b1d6ff861162bf46b6d1c3aa Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 14 Mar 2024 12:49:35 -0400 Subject: [PATCH 201/234] reverted back to standard scan --- daq_macros.py | 2 +- md2_flyers.py | 29 +++++++---------------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 7efa180c..ddc59f5b 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3743,7 +3743,7 @@ def armed_callback(value, old_value, **kwargs): yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states md2.ready_status().wait(timeout=10) logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) + flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) logger.info(f"flyer handoff") yield from bp.fly([flyer]) logger.info(f"fly complete") diff --git a/md2_flyers.py b/md2_flyers.py index f945c5e2..29d7e462 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -32,34 +32,19 @@ def __init__(self, md2, detector=None) -> None: self._collection_dictionary = None def kickoff(self): - # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z - md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], - scan_range=self.collection_params["scan_range"], - exposure_time=self.collection_params["exposure_time"], - start_cx=self.collection_params["start_cx"], - start_cy=self.collection_params["start_cy"], - start_y=self.collection_params["start_y"], - start_z=self.collection_params["start_z"], - stop_cx=self.collection_params["stop_cx"], - stop_cy=self.collection_params["stop_cy"], - stop_y=self.collection_params["stop_y"], - stop_z=self.collection_params["stop_z"],) - logger.info(f"md2 VEC KICKOFF msg: {md2_msg}") + md2_msg = self.md2.standard_scan(num_images=self.collection_params["total_num_images"], + start_angle=self.collection_params["start_angle"], + scan_range=self.collection_params["scan_range"], + exposure_time=self.collection_params["exposure_time"]) + logger.info(f"md2 KICKOFF msg: {md2_msg}") return NullStatus() - def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy): + def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): self.collection_params = { + "total_num_images": total_num_images, "start_angle": start_angle, "scan_range": scan_range, "exposure_time": exposure_time, - "start_cx": start_cx, - "start_cy": start_cy, - "start_y": start_y, - "start_z": start_z, - "stop_cx": stop_cx, - "stop_cy": stop_cy, - "stop_y": stop_y, - "stop_z": stop_z, } def configure_detector(self, file_prefix, data_directory_name): From 8d581eef7e6b23992d825592100d0b8a8925b468 Mon Sep 17 00:00:00 2001 From: NYSBC-Rudra <133771854+NYSBC-Rudra@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:51:45 -0500 Subject: [PATCH 202/234] adding mounting pin check --- gui/control_main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gui/control_main.py b/gui/control_main.py index da18d59d..ad9cb167 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -4646,9 +4646,13 @@ def stopQueueCB(self): def mountSampleCB(self): + + + if getBlConfig("mountEnabled") == 0: self.popupServerMessage("Mounting disabled!! Call staff!") return + logger.info("mount selected sample") self.eraseCB() if ( From 701e3ef58f33b3e5ffe7bb6e35354c5179c62bb4 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Thu, 21 Mar 2024 09:19:03 -0400 Subject: [PATCH 203/234] Extended timeout for collection complete --- md2_flyers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/md2_flyers.py b/md2_flyers.py index 29d7e462..702f416d 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -96,7 +96,7 @@ def complete(self): logger.info(f"FLYER COMPLETE FUNCTION") task_status = self.md2.task_status() logger.info(f"assigning task status") - timeout = self.collection_params["exposure_time"] + 40 + timeout = (self.collection_params["exposure_time"] * 3) + 80 logger.info(f"TASK TIMEOUT: {timeout}") #ready_status.wait(timeout=timeout) task_status.wait(timeout=timeout) From 26916d6c8b23c9581957ba18b14e78ace88037b2 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 25 Mar 2024 12:33:59 -0400 Subject: [PATCH 204/234] removed vectorized parameters from standard collection --- daq_macros.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index ddc59f5b..5c07fb47 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3703,15 +3703,6 @@ def standardDaq(currentRequest): angle_start = sweep_start_angle wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - start_y = md2.y.val() - start_z = md2.z.val() - start_cx = md2.cx.val() - start_cy = md2.cy.val() - stop_cx = md2.cx.val() - stop_cy = md2.cy.val() - stop_y = md2.y.val() - stop_z = md2.z.val() - yield from bps.mv(beamstop.distance_preset, 20.0) md2.save_center() if flyer.detector.cam.armed.get() == 1: From 3c8dc2f00f909ff7cc8c577f34f0ee9bb4a6f1f1 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 25 Mar 2024 12:47:57 -0400 Subject: [PATCH 205/234] Detector distance changes now cause a wait prior to phase transition --- daq_macros.py | 5 +++++ start_bs.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 5c07fb47..a62f396e 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3705,6 +3705,11 @@ def standardDaq(currentRequest): yield from bps.mv(beamstop.distance_preset, 20.0) md2.save_center() + if det_move_done.get() != 1: + def det_move_done_callback(value, old_value, **kwargs): + return (old_value!=1 and value ==1) + det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) + det_move_status.wait() if flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' diff --git a/start_bs.py b/start_bs.py index e8f2258e..d5eb8a95 100755 --- a/start_bs.py +++ b/start_bs.py @@ -210,7 +210,7 @@ class SampleXYZ(Device): govs = _make_governors("XF:19IDC-ES", name="govs") gov_robot = govs.gov.Robot - + det_move_done = EpicsSignalRO("XF:19IDC-ES{Det:1-Ax:Z}Mtr.DMOV", name="det_move_done") #back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") From 3ed36b787b843bcb27dad5d7a9dc548b657b7636 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 25 Mar 2024 12:49:19 -0400 Subject: [PATCH 206/234] added det distance status to all collections --- daq_macros.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index a62f396e..4e362345 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3781,6 +3781,11 @@ def vectorDaq(currentRequest): stop_cy=vector_params["vecEnd"]["finey"] stop_y=vector_params["vecEnd"]["y"] stop_z=vector_params["vecEnd"]["z"] + if det_move_done.get() != 1: + def det_move_done_callback(value, old_value, **kwargs): + return (old_value!=1 and value ==1) + det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) + det_move_status.wait() md2.save_center() yield from bps.mv(beamstop.distance_preset, 20.0) @@ -3872,6 +3877,11 @@ def rasterDaq(rasterReqID): logger.info(f"invert_direction = {invert_direction}") logger.info(f"use_centring_table = {use_centring_table}") logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") + if det_move_done.get() != 1: + def det_move_done_callback(value, old_value, **kwargs): + return (old_value!=1 and value ==1) + det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) + det_move_status.wait() md2.save_center() yield from bps.mv(beamstop.distance_preset, 20.0) From 1752bb8ef6c349f4bdd6bd65d33560f191ac89f2 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 26 Mar 2024 09:32:30 -0400 Subject: [PATCH 207/234] Rounding to solve floating point arithmetic arrors --- daq_macros.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 4e362345..ff682d6c 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -3697,7 +3697,7 @@ def standardDaq(currentRequest): file_number_start = reqObj["file_number_start"] img_width = reqObj["img_width"] exposure_per_image = reqObj["exposure_time"] - total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + total_num_images = int(round(((sweep_end_angle - sweep_start_angle) / img_width), 4)) total_exposure_time = exposure_per_image * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle @@ -3766,7 +3766,7 @@ def vectorDaq(currentRequest): file_number_start = reqObj["file_number_start"] img_width = reqObj["img_width"] exposure_per_image = reqObj["exposure_time"] - total_num_images = int((sweep_end_angle - sweep_start_angle) / img_width) + total_num_images = int(round(((sweep_end_angle - sweep_start_angle) / img_width), 4)) total_exposure_time = reqObj["exposure_time"] * total_num_images scan_range = float(total_num_images)*img_width angle_start = sweep_start_angle From edd5902160df827eb9a83bd87a2b1254957beb77 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Mon, 8 Apr 2024 13:23:43 -0400 Subject: [PATCH 208/234] added titania node task distribution --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index ff682d6c..1acfb5ff 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1004,7 +1004,7 @@ def runDozorThread(directory, time.sleep(10.0) #allow for file writing #node = getNodeName("spot", rowIndex, 8) - node = "titania-cpu002" + node = "titania-cpu00"+((rowIndex+1)%9) if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, From 416e90729d5e39bb91f5f1ba094b55f5348633a6 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 10 Apr 2024 13:10:09 -0400 Subject: [PATCH 209/234] cast to string added --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 1acfb5ff..13e7065d 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1004,7 +1004,7 @@ def runDozorThread(directory, time.sleep(10.0) #allow for file writing #node = getNodeName("spot", rowIndex, 8) - node = "titania-cpu00"+((rowIndex+1)%9) + node = "titania-cpu00"+str((rowIndex+1)%9) if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, From 986e66536468caff7f3f6c47bc4cdb1725398824 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 10 Apr 2024 14:56:50 -0400 Subject: [PATCH 210/234] Added handling for Ophd.utils.WaitTimeoutError --- daq_macros.py | 47 +++++++++++++++++++++++++++++++++++++---------- md2_flyers.py | 15 ++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 13e7065d..36526eb2 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -30,6 +30,7 @@ from threading import Thread from config_params import * from ophyd.status import SubscriptionStatus +from ophyd.utils import WaitTimeoutError from kafka_producer import send_kafka_message import gov_lib @@ -1005,6 +1006,7 @@ def runDozorThread(directory, #node = getNodeName("spot", rowIndex, 8) node = "titania-cpu00"+str((rowIndex+1)%9) + logger.info(f"distributing row {rowIndex} to {node}") if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, @@ -1018,6 +1020,7 @@ def runDozorThread(directory, raise Exception("seqNum seems to be non-standard (<0)") comm_s = f"ssh -q {node} \"{os.environ['MXPROCESSINGSCRIPTSDIR']}dozor.sh {rasterReqID} {rowIndex}\"" + logger.info(f"using the following command: {comm_s}") os.system(comm_s) logger.info('checking for results on remote node: %s' % comm_s) logger.info("leaving thread") @@ -3728,8 +3731,12 @@ def armed_callback(value, old_value, **kwargs): arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) flyer.detector.cam.acquire.put(1) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait(timeout=20) - govStatus.wait(timeout=20) + try: + arm_status.wait(timeout=20) + govStatus.wait(timeout=20) + except WaitTimeoutError: + logger.error("Timeout during arming or governor move, aborting collection") + return logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") @@ -3737,7 +3744,11 @@ def armed_callback(value, old_value, **kwargs): flyer.detector.stage() start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=10) + try: + md2.ready_status().wait(timeout=10) + except WaitTimeoutError: + logger.error("timeout: md2 failed to enter ready state, aborting collection") + return logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) logger.info(f"flyer handoff") @@ -3806,8 +3817,12 @@ def armed_callback(value, old_value, **kwargs): arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) vector_flyer.detector.cam.acquire.put(1) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait(timeout=10) - govStatus.wait(timeout=20) + try: + arm_status.wait(timeout=10) + govStatus.wait(timeout=20) + except WaitTimeoutError: + logger.error("Timeout reached during arming or governor move, aborting") + return logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") @@ -3815,7 +3830,11 @@ def armed_callback(value, old_value, **kwargs): flyer.detector.stage() start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=10) + try: + md2.ready_status().wait(timeout=10) + except WaitTimeoutError: + logger.error("timeout: md2 failed to reach ready state, aborting") + return logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) yield from bp.fly([vector_flyer]) @@ -3854,7 +3873,7 @@ def rasterDaq(rasterReqID): start_cx = md2.cx.val()# + (xEnd/1000) start_cy = md2.cy.val() frames_per_line = numsteps - total_exposure_time = exposure_per_image * total_num_images + total_exposure_time = exposure_per_image * frames_per_line invert_direction = False use_centring_table = True use_fast_mesh_scans = True @@ -3902,8 +3921,12 @@ def armed_callback(value, old_value, **kwargs): arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) raster_flyer.detector.cam.acquire.put(1) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - arm_status.wait(timeout=10) - govStatus.wait(timeout=20) + try: + arm_status.wait(timeout=10) + govStatus.wait(timeout=20) + except WaitTimeoutError: + logger.error("arming or governor status failure") + return logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") @@ -3911,7 +3934,11 @@ def armed_callback(value, old_value, **kwargs): flyer.detector.stage() start_time = time.time() yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - md2.ready_status().wait(timeout=10) + try: + md2.ready_status().wait(timeout=10) + except: + logger.error("md2 failed to reach ready state, aborting collection") + return logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) yield from bp.fly([raster_flyer]) diff --git a/md2_flyers.py b/md2_flyers.py index 702f416d..01021d65 100644 --- a/md2_flyers.py +++ b/md2_flyers.py @@ -5,6 +5,7 @@ import grp from ophyd.sim import NullStatus from ophyd.status import SubscriptionStatus +from ophyd.utils import WaitTimeoutError logger = logging.getLogger(__name__) @@ -93,15 +94,19 @@ def complete(self): #logger.info(f"TASK INFO[6]: {self.md2.task_info[6]}") #logger.info(f"TASK OUTPUT: {self.md2.task_output}") - logger.info(f"FLYER COMPLETE FUNCTION") + logger.debug(f"FLYER COMPLETE FUNCTION") task_status = self.md2.task_status() - logger.info(f"assigning task status") + logger.debug(f"assigning task status") timeout = (self.collection_params["exposure_time"] * 3) + 80 logger.info(f"TASK TIMEOUT: {timeout}") #ready_status.wait(timeout=timeout) - task_status.wait(timeout=timeout) - logger.info(f"TASK INFO: {self.md2.task_info}") - logger.info(f"TASK OUTPUT: {self.md2.task_output}") + try: + task_status.wait(timeout=timeout) + except WaitTimeoutError: + logger.info("reached task timeout") + logger.info(f"TASK INFO: {self.md2.task_info}") + logger.info(f"TASK OUTPUT: {self.md2.task_output}") + return return task_status def describe_collect(self): From dc9730434bb376594c50a9e6d608d4cf7e1be068 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 10 Apr 2024 16:20:47 -0400 Subject: [PATCH 211/234] always redraw rasters and allow raster inversion --- gui/control_main.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index ad9cb167..93028473 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3202,7 +3202,11 @@ def fillPolyRaster( ): # this is trying to figure out row direction cellIndex = spotLineCounter else: - if i % 2 == 0: # this is trying to figure out row direction + if daq_utils.beamline == "nyx": + is_raster_inverted = 1 + else: + is_raster_inverted = 0 + if i % 2 == is_raster_inverted: # this is trying to figure out row direction cellIndex = spotLineCounter else: cellIndex = rowStartIndex + ((numsteps - 1) - j) @@ -4768,12 +4772,15 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): else: self.rasterGrainCustomRadio.setChecked(True) rasterStep = int(reqObj["gridStep"]) + logger.info("reaching redraw now") if not self.hideRastersCheckBox.isChecked() and ( reqObj["protocol"] in ("raster", "stepRaster", "multiCol") ): - if not self.rasterIsDrawn(selectedSampleRequest): - self.drawPolyRaster(selectedSampleRequest) - self.fillPolyRaster(selectedSampleRequest) + #if not self.rasterIsDrawn(selectedSampleRequest): + # always erase and then draw + self.eraseRastersCB() + self.drawPolyRaster(selectedSampleRequest) + self.fillPolyRaster(selectedSampleRequest) if ( str(self.govStateMessagePV.get(as_string=True)) == "state SA" @@ -4781,6 +4788,7 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): and self.selectedSampleRequest["sample"] # with control enabled == self.mountedPin_pv.get() ): # And the sample of the selected request is mounted + logger.info("attempting to move to raster start") self.processSampMove(self.gon.x.val(), "x") self.processSampMove(self.gon.y.val(), "y") self.processSampMove(self.gon.z.val(), "z") From 033b454d02a63b90b22fa0351764d560357fba5a Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Wed, 10 Apr 2024 16:21:27 -0400 Subject: [PATCH 212/234] add delay to evade network security blocking rapid SSH calls --- daq_macros.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 36526eb2..643dc78d 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1005,7 +1005,7 @@ def runDozorThread(directory, time.sleep(10.0) #allow for file writing #node = getNodeName("spot", rowIndex, 8) - node = "titania-cpu00"+str((rowIndex+1)%9) + node = "titania-cpu00"+str((rowIndex%4)+1) logger.info(f"distributing row {rowIndex} to {node}") if (seqNum>-1): #eiger @@ -3948,6 +3948,7 @@ def armed_callback(value, old_value, **kwargs): rasterFilePrefix = rasterFilePrefix.split("/")[-1] logger.info(f"raster prefix {rasterFilePrefix}") for i in range(0, number_of_lines): + time.sleep(1.0) row_index = i logger.info(f'spot finding for row {i}') seqNum = raster_flyer.detector.cam.sequence_id.get() From 13307c3fd2449ee6c0e8438e1ade9120a1f4dc32 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 29 Apr 2024 18:29:29 -0400 Subject: [PATCH 213/234] Resolution calculator cleanup --- gui/dialog/__init__.py | 8 +- gui/dialog/resolution_calculator.py | 97 ------- gui/dialog/resolution_dialog.py | 436 +++++++++++++--------------- utils/resolution_calculator.py | 79 +++++ 4 files changed, 289 insertions(+), 331 deletions(-) delete mode 100644 gui/dialog/resolution_calculator.py create mode 100644 utils/resolution_calculator.py diff --git a/gui/dialog/__init__.py b/gui/dialog/__init__.py index 177170f2..08dc984c 100644 --- a/gui/dialog/__init__.py +++ b/gui/dialog/__init__.py @@ -1,10 +1,14 @@ -from .snap_comment import SnapCommentDialog +from .dewar import DewarDialog +from .puck_dialog import PuckDialog from .raster_explore import RasterExploreDialog +from .resolution_dialog import CalculatorWindow +from .screen_defaults import ScreenDefaultsDialog +from .set_energy import SetEnergyDialog +from .snap_comment import SnapCommentDialog from .staff_screen import StaffScreenDialog from .user_screen import UserScreenDialog from .puck_dialog import PuckDialog from .dewar import DewarDialog from .screen_defaults import ScreenDefaultsDialog from .set_energy import SetEnergyDialog -from .resolution_calculator import Calculator from .resolution_dialog import CalculatorWindow diff --git a/gui/dialog/resolution_calculator.py b/gui/dialog/resolution_calculator.py deleted file mode 100644 index 394f9ab9..00000000 --- a/gui/dialog/resolution_calculator.py +++ /dev/null @@ -1,97 +0,0 @@ -import math -import sys - - -class Calculator: - """ - Make a calculator object that can calculate resolution formulas (and nothing else) - - """ - def __init__(self): - self.r = None - self.d = None - self.L = None - self.theta = None - self.wavelength = None - - def set_all_variables(self, variable_dict): - for key in variable_dict: - self.set_variables(key, variable_dict[key]) - - - - def set_variables(self, name, value): - if name == 'r': - self.r = value - elif name == 'd': - self.d = value - elif name == 'L': - self.L = value - elif name == 'theta': - self.theta = value - elif name == 'wavelength': - self.wavelength = value - - def calcD(self, r = None, L = None, wavelength = None, theta = None): - r = r or self.r - L = L or self.L - wavelength = wavelength or self.wavelength - theta = theta or self.theta - try: - denominator = 2*(math.sin((0.5*math.atan(r/L)) + theta)) - numerator = wavelength - return numerator/denominator - except Exception as e: - return e - - - - - def calcL(self, r = None, d = None, wavelength = None, theta = None): - r = r or self.r - d = d or self.d - wavelength = wavelength or self.wavelength - theta = theta or self.theta - try: - denominator = math.tan((2* math.asin(wavelength/(2*d) ) ) -(2*theta) ) - numerator = r - return numerator/denominator - except Exception as e: - return e - - - def calcTheta(self, r = None, L = None, wavelength = None, d = None): - r = r or self.r - L = L or self.L - wavelength = wavelength or self.wavelength - d = d or self.d - try: - val1 = math.asin(wavelength/(2*d)) - val2 = 0.5*math.atan(r/L) - return val1 - val2 - except Exception as e: - return e - - - def calcWavelength(self, r = None, L = None, d = None, theta = None): - r = r or self.r - L = L or self.L - d = d or self.d - theta = theta or self.theta - valin = 0.5*math.atan(r/L) - try: - wavelength = 2*d * math.sin(valin + theta) - return wavelength - except Exception as e: - return e - - - - - - - - - - - \ No newline at end of file diff --git a/gui/dialog/resolution_dialog.py b/gui/dialog/resolution_dialog.py index f1fab0af..25306a52 100644 --- a/gui/dialog/resolution_dialog.py +++ b/gui/dialog/resolution_dialog.py @@ -1,247 +1,219 @@ -from qtpy.QtWidgets import * -from qtpy import QtCore -from qtpy import QtGui -from qtpy import QtWidgets -from qtpy.QtCore import * -from qtpy.QtGui import * -import sys -from gui.dialog import Calculator import typing +from qtpy import QtWidgets +from qtpy.QtGui import QDoubleValidator +from qtpy.QtWidgets import QLabel, QLineEdit, QPushButton, QRadioButton, QVBoxLayout + +from utils.resolution_calculator import Calculator + if typing.TYPE_CHECKING: from lsdcGui import ControlMain WINDOW_SIZE = 480 -#main qtpy window the calculator exists in +# main qtpy window the calculator exists in class CalculatorWindow(QtWidgets.QDialog): - def __init__(self, parent: "ControlMain"): - super(CalculatorWindow, self).__init__(parent) - self.setFixedSize(WINDOW_SIZE,WINDOW_SIZE) - #making radio buttons to choose formula - self.buttonDictionary = {'L': {'picker' : QRadioButton('Caluclate crystal to detector distance')}, - 'd': {'picker': QRadioButton("Calculate resolution")} , - 'theta': {'picker':QRadioButton("Calculate detector 2theta")}, - 'wavelength': {'picker':QRadioButton("Calculate wavelength")}, - 'r':{'value':None}} - - #making lines to hold inputs - # self.r_value_enter = QComboBox() - # self.r_value_enter.setToolTip("Detector Distance") - # self.r_value_enter = QLineEdit() - # self.r_value_enter.setPlaceholderText('Set r value (in mm)') - # self.buttonDictionary['r']['value'] = self.r_value_enter - # self.r_value_enter.setCurrentIndex(1) - - - self.r_value_enter = QLineEdit() - self.r_value_enter.setPlaceholderText('Set r value') - self.buttonDictionary['r']['value'] = self.r_value_enter - self.r_value_enter.setValidator(QDoubleValidator()) - #setting inputs to Double only - - self.L_value_enter = QLineEdit() - self.L_value_enter.setPlaceholderText('Set L value') - self.buttonDictionary['L']['value'] = self.L_value_enter - self.L_value_enter.setValidator(QDoubleValidator()) - - self.d_value_enter = QLineEdit() - self.d_value_enter.setPlaceholderText('Set d value') - self.buttonDictionary['d']['value'] = self.d_value_enter - self.d_value_enter.setValidator(QDoubleValidator()) - - self.theta_value_enter = QLineEdit() - self.theta_value_enter.setPlaceholderText('Set theta value') - self.buttonDictionary['theta']['value'] = self.theta_value_enter - self.theta_value_enter.setValidator(QDoubleValidator()) - - self.wave_value_enter = QLineEdit() - self.wave_value_enter.setPlaceholderText('Set wavelength value') - self.buttonDictionary['wavelength']['value'] = self.wave_value_enter - self.wave_value_enter.setValidator(QDoubleValidator()) - - - self.final_button = QPushButton('Calculate', self) - self.final_button.clicked.connect(self.calculateValue) - - self.bottom_text = QLabel() - self.bottom_text.setText('Enter values and Press button to calculate') - - - #creating calculator object - self.calculator = Calculator() - - - - layout = QVBoxLayout() - layout.addWidget(self.r_value_enter) - for key in self.buttonDictionary: - if 'picker' in self.buttonDictionary[key].keys(): - layout.addWidget(self.buttonDictionary[key]['picker']) - layout.addWidget(self.buttonDictionary[key]['value']) - layout.addWidget(self.final_button) - layout.addWidget(self.bottom_text) - self.setLayout(layout) - #self._createDisplay() - - - - # def _createButtons(self): - # buttonsLayout = QGridLayout() - # self.formula_picker = QRadioButton('Formula') - # self.b2 = QRadioButton("Button2") - - ''' + def __init__(self, parent: "ControlMain"): + super(CalculatorWindow, self).__init__(parent) + self.setFixedSize(WINDOW_SIZE, WINDOW_SIZE) + # making radio buttons to choose formula + self.buttonDictionary = { + "L": {"picker": QRadioButton("Caluclate crystal to detector distance")}, + "d": {"picker": QRadioButton("Calculate resolution")}, + "theta": {"picker": QRadioButton("Calculate detector 2theta")}, + "wavelength": {"picker": QRadioButton("Calculate wavelength")}, + "r": {"value": None}, + } + + self.r_value_enter = QLineEdit() + self.r_value_enter.setPlaceholderText("Set r value") + self.buttonDictionary["r"]["value"] = self.r_value_enter + self.r_value_enter.setValidator(QDoubleValidator()) + # setting inputs to Double only + + self.L_value_enter = QLineEdit() + self.L_value_enter.setPlaceholderText("Set L value") + self.buttonDictionary["L"]["value"] = self.L_value_enter + self.L_value_enter.setValidator(QDoubleValidator()) + + self.d_value_enter = QLineEdit() + self.d_value_enter.setPlaceholderText("Set d value") + self.buttonDictionary["d"]["value"] = self.d_value_enter + self.d_value_enter.setValidator(QDoubleValidator()) + + self.theta_value_enter = QLineEdit() + self.theta_value_enter.setPlaceholderText("Set theta value") + self.buttonDictionary["theta"]["value"] = self.theta_value_enter + self.theta_value_enter.setValidator(QDoubleValidator()) + + self.wave_value_enter = QLineEdit() + self.wave_value_enter.setPlaceholderText("Set wavelength value") + self.buttonDictionary["wavelength"]["value"] = self.wave_value_enter + self.wave_value_enter.setValidator(QDoubleValidator()) + + self.final_button = QPushButton("Calculate", self) + self.final_button.clicked.connect(self.calculateValue) + + self.bottom_text = QLabel() + self.bottom_text.setText("Enter values and Press button to calculate") + + # creating calculator object + self.calculator = Calculator() + + layout = QVBoxLayout() + layout.addWidget(self.r_value_enter) + for key in self.buttonDictionary: + if "picker" in self.buttonDictionary[key].keys(): + layout.addWidget(self.buttonDictionary[key]["picker"]) + layout.addWidget(self.buttonDictionary[key]["value"]) + layout.addWidget(self.final_button) + layout.addWidget(self.bottom_text) + self.setLayout(layout) + + """ calls resolution calculator to calculate value depending on inputs from widgets -outputs -value_to_return = value from formula calculated if no problems -returns nothing if a problem occured, changes bottom_text - ''' - - def calculateValue(self): - checked_key = None - #checking which formula to use - for key in self.buttonDictionary: - if key != 'r' and self.buttonDictionary[key]['picker'].isChecked(): - checked_key = key - if checked_key == None: - self.bottom_text.setText("No calculation specified (press one of the radio buttons)") - return - - #getting values from textboxes r_value text box - # r_value = self.r_value_enter.currentIndex() - # convertValues = [200,244.7] - # # print("r value = {}".format(r_value)) - # #checking if value is a number string or empty string - # r_value = convertValues[r_value] - # r_value = float(r_value) - - r_value = self.r_value_enter.displayText() - # checking if value is a number string or empty string - if r_value == "" or r_value[0].isalpha() == True: - self.bottom_text.setText("formula to calculate {} requires r value".format(checked_key)) - return - elif float(r_value) < 140 or float(r_value) > 350: - self.bottom_text.setText("r value must be between 140 and 350") - return - - r_value = float(r_value) - - - - d_value = self.d_value_enter.displayText() - #checking if value is string or none if not calculating that value (trying to use .isalpha but not when value is None) - if ((d_value == "" or d_value[0].isalpha() == True) and checked_key != 'd') : - self.bottom_text.setText("formula to calculate {} requires d value".format(checked_key)) - return - - l_value = self.L_value_enter.displayText() - if ((l_value == "" or l_value[0].isalpha() == True) and checked_key != 'L'): - self.bottom_text.setText("formula to calculate {} requires L value".format(checked_key)) - return - - theta_value = self.theta_value_enter.displayText() - if ((theta_value == "" or theta_value[0].isalpha() == True)and checked_key != 'theta'): - self.bottom_text.setText("formula to calculate {} requires theta value".format(checked_key)) - return - - wave_value = self.wave_value_enter.displayText() - if ((wave_value == "" or wave_value[0].isalpha() == True) and checked_key != 'wavelength'): - self.bottom_text.setText("formula to calculate {} requires the wavelenght".format(checked_key)) - return - - - #setting value to return if want value returned - value_to_return = None - - if checked_key == 'd': - l_value = float(self.L_value_enter.displayText()) - theta_value = float(self.theta_value_enter.displayText()) - wave_value = float(self.wave_value_enter.displayText()) - - - - - - variableDict = {'L':l_value, 'theta': theta_value, 'wavelength': wave_value, 'r': r_value} - - self.calculator.set_all_variables(variableDict) - d_value = self.calculator.calcD() - value_to_return = d_value - self.d_value_enter.setText(str(d_value)) - self.calculator.set_variables('d', d_value) - - - - - elif checked_key == 'L': - - d_value = float(self.d_value_enter.displayText()) - theta_value = float(self.theta_value_enter.displayText()) - wave_value = float(self.wave_value_enter.displayText()) - - - - - variableDict = {'d':d_value, 'theta': theta_value, 'wavelength': wave_value, 'r': r_value} - - self.calculator.set_all_variables(variableDict) - L_value = self.calculator.calcL() - value_to_return = L_value - self.L_value_enter.setText(str(L_value)) - self.calculator.set_variables('L', L_value) - - elif checked_key == 'theta': - - l_value = float(self.L_value_enter.displayText()) - d_value = float(self.d_value_enter.displayText()) - wave_value = float(self.wave_value_enter.displayText()) - - - variableDict = {'L':l_value, 'd': d_value, 'wavelength': wave_value, 'r': r_value} - - self.calculator.set_all_variables(variableDict) - theta_value = self.calculator.calcTheta() - value_to_return = theta_value - self.theta_value_enter.setText(str(theta_value)) - self.calculator.set_variables('theta', theta_value) - - - - elif checked_key == 'wavelength': - - l_value = float(self.L_value_enter.displayText()) - theta_value = float(self.theta_value_enter.displayText()) - d_value = float(self.d_value_enter.displayText()) - variableDict = {'L':l_value, 'd': d_value, 'theta': theta_value, 'r': r_value} - - self.calculator.set_all_variables(variableDict) - wave_value = self.calculator.calcWavelength() - self.calculator.set_variables('wavelength', wave_value) - value_to_return = wave_value - self.wave_value_enter.setText(str(wave_value)) - - - - - - - - - self.bottom_text.setText("- Done Calculating - \n {} value = {}".format(checked_key, value_to_return)) - return value_to_return - - - - - - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = CalculatorWindow() - window.show() - - app.exec() \ No newline at end of file + """ + + def calculateValue(self): + checked_key = None + # checking which formula to use + for key in self.buttonDictionary: + if key != "r" and self.buttonDictionary[key]["picker"].isChecked(): + checked_key = key + if not checked_key: + self.bottom_text.setText( + "No calculation specified (press one of the radio buttons)" + ) + return + + r_value = self.r_value_enter.displayText() + # checking if value is a number string or empty string + if r_value == "" or r_value[0].isalpha(): + self.bottom_text.setText( + "formula to calculate {} requires r value".format(checked_key) + ) + return + elif float(r_value) < 140 or float(r_value) > 350: + self.bottom_text.setText("r value must be between 140 and 350") + return + + r_value = float(r_value) + + d_value = self.d_value_enter.displayText() + # checking if value is string or none if not calculating that value (trying to use .isalpha but not when value is None) + if (d_value == "" or d_value[0].isalpha()) and checked_key != "d": + self.bottom_text.setText( + "formula to calculate {} requires d value".format(checked_key) + ) + return + + l_value = self.L_value_enter.displayText() + if (l_value == "" or l_value[0].isalpha()) and checked_key != "L": + self.bottom_text.setText( + "formula to calculate {} requires L value".format(checked_key) + ) + return + + theta_value = self.theta_value_enter.displayText() + if (theta_value == "" or theta_value[0].isalpha()) and checked_key != "theta": + self.bottom_text.setText( + "formula to calculate {} requires theta value".format(checked_key) + ) + return + + wave_value = self.wave_value_enter.displayText() + if ( + wave_value == "" or wave_value[0].isalpha() + ) and checked_key != "wavelength": + self.bottom_text.setText( + "formula to calculate {} requires the wavelenght".format(checked_key) + ) + return + + # setting value to return if want value returned + value_to_return = None + + if checked_key == "d": + l_value = float(self.L_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + variableDict = { + "L": l_value, + "theta": theta_value, + "wavelength": wave_value, + "r": r_value, + } + + self.calculator.set_all_variables(variableDict) + d_value = self.calculator.calcD() + value_to_return = d_value + self.d_value_enter.setText(str(d_value)) + self.calculator.set_variables("d", d_value) + + elif checked_key == "L": + + d_value = float(self.d_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + variableDict = { + "d": d_value, + "theta": theta_value, + "wavelength": wave_value, + "r": r_value, + } + + self.calculator.set_all_variables(variableDict) + L_value = self.calculator.calcL() + value_to_return = L_value + self.L_value_enter.setText(str(L_value)) + self.calculator.set_variables("L", L_value) + + elif checked_key == "theta": + + l_value = float(self.L_value_enter.displayText()) + d_value = float(self.d_value_enter.displayText()) + wave_value = float(self.wave_value_enter.displayText()) + + variableDict = { + "L": l_value, + "d": d_value, + "wavelength": wave_value, + "r": r_value, + } + + self.calculator.set_all_variables(variableDict) + theta_value = self.calculator.calcTheta() + value_to_return = theta_value + self.theta_value_enter.setText(str(theta_value)) + self.calculator.set_variables("theta", theta_value) + + elif checked_key == "wavelength": + + l_value = float(self.L_value_enter.displayText()) + theta_value = float(self.theta_value_enter.displayText()) + d_value = float(self.d_value_enter.displayText()) + variableDict = { + "L": l_value, + "d": d_value, + "theta": theta_value, + "r": r_value, + } + + self.calculator.set_all_variables(variableDict) + wave_value = self.calculator.calcWavelength() + self.calculator.set_variables("wavelength", wave_value) + value_to_return = wave_value + self.wave_value_enter.setText(str(wave_value)) + + self.bottom_text.setText( + "- Done Calculating - \n {} value = {}".format(checked_key, value_to_return) + ) + return value_to_return diff --git a/utils/resolution_calculator.py b/utils/resolution_calculator.py new file mode 100644 index 00000000..e0216fb3 --- /dev/null +++ b/utils/resolution_calculator.py @@ -0,0 +1,79 @@ +import math + + +class Calculator: + """ + Make a calculator object that can calculate resolution formulas (and nothing else) + + """ + + def __init__(self): + self.r = None + self.d = None + self.L = None + self.theta = None + self.wavelength = None + + def set_all_variables(self, variable_dict): + for key in variable_dict: + self.set_variables(key, variable_dict[key]) + + def set_variables(self, name, value): + if name == "r": + self.r = value + elif name == "d": + self.d = value + elif name == "L": + self.L = value + elif name == "theta": + self.theta = value + elif name == "wavelength": + self.wavelength = value + + def calcD(self, r=None, L=None, wavelength=None, theta=None): + r = r or self.r + L = L or self.L + wavelength = wavelength or self.wavelength + theta = theta or self.theta + try: + denominator = 2 * (math.sin((0.5 * math.atan(r / L)) + theta)) + numerator = wavelength + return numerator / denominator + except Exception as e: + return e + + def calcL(self, r=None, d=None, wavelength=None, theta=None): + r = r or self.r + d = d or self.d + wavelength = wavelength or self.wavelength + theta = theta or self.theta + try: + denominator = math.tan((2 * math.asin(wavelength / (2 * d))) - (2 * theta)) + numerator = r + return numerator / denominator + except Exception as e: + return e + + def calcTheta(self, r=None, L=None, wavelength=None, d=None): + r = r or self.r + L = L or self.L + wavelength = wavelength or self.wavelength + d = d or self.d + try: + val1 = math.asin(wavelength / (2 * d)) + val2 = 0.5 * math.atan(r / L) + return val1 - val2 + except Exception as e: + return e + + def calcWavelength(self, r=None, L=None, d=None, theta=None): + r = r or self.r + L = L or self.L + d = d or self.d + theta = theta or self.theta + valin = 0.5 * math.atan(r / L) + try: + wavelength = 2 * d * math.sin(valin + theta) + return wavelength + except Exception as e: + return e From 60d00b0bbdba35b3b50f477c4fcf06a3b147912e Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 29 Apr 2024 18:41:26 -0400 Subject: [PATCH 214/234] Move md2 devices to mxbluesky.devices.md2 --- beamline_support.py | 2 +- gui/control_main.py | 2 +- devices.py => mxbluesky/devices/md2.py | 292 +++++++++++++++---------- start_bs.py | 2 +- 4 files changed, 181 insertions(+), 117 deletions(-) rename devices.py => mxbluesky/devices/md2.py (50%) diff --git a/beamline_support.py b/beamline_support.py index 104fc25e..eb8e2cf1 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -4,7 +4,7 @@ import ophyd from ophyd import EpicsMotor from ophyd import EpicsScaler -from devices import MD2Positioner +from mxbluesky.devices.md2 import MD2Positioner import time import epics import os diff --git a/gui/control_main.py b/gui/control_main.py index 93028473..522255ea 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -22,7 +22,7 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from devices import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice +from mxbluesky.devices.md2 import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice import daq_utils import db_lib diff --git a/devices.py b/mxbluesky/devices/md2.py similarity index 50% rename from devices.py rename to mxbluesky/devices/md2.py index a751d6d1..7fa98c59 100644 --- a/devices.py +++ b/mxbluesky/devices/md2.py @@ -1,28 +1,32 @@ -from ophyd import Device, Component as Cpt, EpicsSignal, EpicsSignalRO, PVPositioner +import os import socket + +from ophyd import Component as Cpt +from ophyd import Device, EpicsSignal, EpicsSignalRO, PVPositioner from ophyd.status import SubscriptionStatus -import os + class MD2Positioner(PVPositioner): - setpoint = Cpt(EpicsSignal, 'Position', name='setpoint') - readback = Cpt(EpicsSignal, 'Position', name='readback') - state = Cpt(EpicsSignalRO, 'State', name='state') - done = Cpt(EpicsSignalRO, 'State', name='done') - precision = Cpt(EpicsSignalRO, 'Precision', name='precision') - done_value = 4 # MD2 Enum, 4 = Ready + setpoint = Cpt(EpicsSignal, "Position", name="setpoint") + readback = Cpt(EpicsSignal, "Position", name="readback") + state = Cpt(EpicsSignalRO, "State", name="state") + done = Cpt(EpicsSignalRO, "State", name="done") + precision = Cpt(EpicsSignalRO, "Precision", name="precision") + done_value = 4 # MD2 Enum, 4 = Ready # TODO: Add limits, settle_time, timeout or defaults for each def val(self): return self.get().readback + class ExporterComponent(Cpt): def __init__(self, address, port, name, **kwargs): super().__init__(self, **kwargs) self.name = name self.address = address self.port = port - self.sock = None # socket.socket(socket.AF_INET, socket.SOCK_STREAM) - #self.sock.settimeout(5) + self.sock = None # socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # self.sock.settimeout(5) def get(self): return () @@ -44,9 +48,9 @@ def send_data(self, data): s.connect((self.address, self.port)) s.sendto(data.encode(), (self.address, self.port)) state = s.recv(4096) - print(f'state: {self.decipher_reply(state)}') + print(f"state: {self.decipher_reply(state)}") ret = None - while ret == None: + while ret is None: output = s.recv(4096) output = self.decipher_reply(output) ret = self.process_ret(output) @@ -54,21 +58,21 @@ def send_data(self, data): def read_stream(self): output = None - while output == None: + while output is None: output, addr = self.sock.recvfrom(4096) - print(f'output:{self.decipher_reply(output)}') + print(f"output:{self.decipher_reply(output)}") output = None def write(self, attribute, value): - return self.send_data('WRTE ' + attribute + ' ' + str(value)) + return self.send_data("WRTE " + attribute + " " + str(value)) def read(self, attribute): - return (self.send_data('READ ' + attribute + ' ')).split(" ")[0][4:] + return (self.send_data("READ " + attribute + " ")).split(" ")[0][4:] def cmd(self, method, parameters): parameters = map(str, parameters) params = "\t".join(parameters) - return self.send_data('EXEC ' + method + ' ' + str(params)) + return self.send_data("EXEC " + method + " " + str(params)) def decipher_reply(self, reply): # Specifically for decoding MD2 Exporter replies @@ -80,7 +84,7 @@ def decipher_reply(self, reply): return reply[2:-1] def process_ret(self, ret): - # Returns only when an error or return value is found, + # Returns only when an error or return value is found, # ignores events to ensure flyer kickoff functions as expected for line in ret.split("\n"): if "ERR:" in line: @@ -92,14 +96,15 @@ def process_ret(self, ret): elif "NULL" in line: print(f"null error: {line}") return line - elif "EVT:" in line: # print here if you want to see the events - pass #return self.process_evt(line) + elif "EVT:" in line: # print here if you want to see the events + pass # return self.process_evt(line) + class LightDevice(Device): - control = Cpt(EpicsSignal, 'LightIsOn', name='control') - factor = Cpt(EpicsSignal, 'LightFactor', name='factor') - level = Cpt(EpicsSignal, 'LightLevel', name='level') - + control = Cpt(EpicsSignal, "LightIsOn", name="control") + factor = Cpt(EpicsSignal, "LightFactor", name="factor") + level = Cpt(EpicsSignal, "LightLevel", name="level") + def is_on(self): return self.control.get() == 1 @@ -111,10 +116,11 @@ def turn_off(self): def set_factor(self, factor): self.factor.set(factor) - + def set_level(self, level): self.level.set(level) + class BeamstopDevice(Device): distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") distance_preset = Cpt(EpicsSignal, "BeamstopDistance", name="distance_preset") @@ -123,6 +129,7 @@ class BeamstopDevice(Device): z = Cpt(MD2Positioner, "BeamstopZ", name="z") pos = Cpt(EpicsSignal, "BeamstopPosition", name="pos") + class MD2SimpleHVDevice(Device): horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") @@ -134,32 +141,40 @@ class MD2SimpleHVDevice(Device): # 2: OFF, just below the OAV. # 3: UNKNOWN, not in a predefined position (this cannot be set). + class GonioDevice(Device): - omega = Cpt(MD2Positioner, 'Omega',name='omega') - x = Cpt(MD2Positioner, 'AlignmentX',name='x') - y = Cpt(MD2Positioner, 'AlignmentY',name='y') - z = Cpt(MD2Positioner, 'AlignmentZ',name='z') - cx = Cpt(MD2Positioner, 'CentringX',name='cx') - cy = Cpt(MD2Positioner, 'CentringY',name='cy') + omega = Cpt(MD2Positioner, "Omega", name="omega") + x = Cpt(MD2Positioner, "AlignmentX", name="x") + y = Cpt(MD2Positioner, "AlignmentY", name="y") + z = Cpt(MD2Positioner, "AlignmentZ", name="z") + cx = Cpt(MD2Positioner, "CentringX", name="cx") + cy = Cpt(MD2Positioner, "CentringY", name="cy") + class MD2Device(GonioDevice): # TODO: Enum for MD2 phases and states - cx = Cpt(MD2Positioner, 'CentringX',name='cx') - cy = Cpt(MD2Positioner, 'CentringY',name='cy') - center_pixel_x = Cpt(EpicsSignalRO, 'BeamPositionHorizontal',name='center_pixel_x') - center_pixel_y = Cpt(EpicsSignalRO, 'BeamPositionVertical',name='center_pixel_y') - centring_click = Cpt(EpicsSignal, 'setCentringClick',name='centring_click') - centring_save = Cpt(EpicsSignal, 'saveCentringPositions', name='centring_save') - state = Cpt(EpicsSignalRO, 'State',name='state') - phase = Cpt(EpicsSignal, 'CurrentPhase',name='phase') - phase_index = Cpt(EpicsSignalRO, 'CurrentPhaseIndex',name='phase_index') - detector_state = Cpt(EpicsSignal, 'DetectorState',name='det_state') - detector_gate_pulse_enabled = Cpt(EpicsSignal, 'DetectorGatePulseEnabled',name='det_gate_pulse_enabled') - exporter = Cpt(ExporterComponent, address=os.environ['EXPORTER_HOST'], port=int(os.environ['EXPORTER_PORT']), name='exporter') - - task_info = Cpt(EpicsSignalRO, 'LastTaskInfo',name='task_info') - task_output = Cpt(EpicsSignalRO, 'LastTaskOutput', name='task_output') - + cx = Cpt(MD2Positioner, "CentringX", name="cx") + cy = Cpt(MD2Positioner, "CentringY", name="cy") + center_pixel_x = Cpt(EpicsSignalRO, "BeamPositionHorizontal", name="center_pixel_x") + center_pixel_y = Cpt(EpicsSignalRO, "BeamPositionVertical", name="center_pixel_y") + centring_click = Cpt(EpicsSignal, "setCentringClick", name="centring_click") + centring_save = Cpt(EpicsSignal, "saveCentringPositions", name="centring_save") + state = Cpt(EpicsSignalRO, "State", name="state") + phase = Cpt(EpicsSignal, "CurrentPhase", name="phase") + phase_index = Cpt(EpicsSignalRO, "CurrentPhaseIndex", name="phase_index") + detector_state = Cpt(EpicsSignal, "DetectorState", name="det_state") + detector_gate_pulse_enabled = Cpt( + EpicsSignal, "DetectorGatePulseEnabled", name="det_gate_pulse_enabled" + ) + exporter = Cpt( + ExporterComponent, + address=os.environ["EXPORTER_HOST"], + port=int(os.environ["EXPORTER_PORT"]), + name="exporter", + ) + + task_info = Cpt(EpicsSignalRO, "LastTaskInfo", name="task_info") + task_output = Cpt(EpicsSignalRO, "LastTaskOutput", name="task_output") def save_center(self): self.centring_save.put("__EMPTY__") @@ -170,7 +185,8 @@ def task_complete(self): def task_status(self): def check_task(*, old_value, value, **kwargs): "Return True when last task completes" - return (value[6] == '1') + return value[6] == "1" + return SubscriptionStatus(self.task_info, check_task) def is_ready(self): @@ -180,44 +196,59 @@ def ready_status(self): # returns an ophyd status object that monitors the state pv for operations to complete def check_ready(*, old_value, value, **kwargs): "Return True when the MD2 is ready" - return (value == 4) + return value == 4 + return SubscriptionStatus(self.state, check_ready) - + def phase_transition(self, phase): # returns an ophyd status object that monitors the phase pv for operations to complete self.phase.put(phase) + def check_transition_state(*, old_value, value, **kwargs): "Return True when the MD2 is ready" - return (value == 4 and self.phase.get() == phase) + return value == 4 and self.phase.get() == phase + return SubscriptionStatus(self.state, check_transition_state) - def standard_scan(self, - frame_number=0, # int: frame ID just for logging purposes. - num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. - start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=0.1, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=0.1, # double: exposure time (sec) to control shutter command. - num_passes=1 # int: number of moves forward and reverse between start angle and stop angle - ): - command = 'startScanEx2' + def standard_scan( + self, + frame_number=0, # int: frame ID just for logging purposes. + num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. + start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=0.1, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=0.1, # double: exposure time (sec) to control shutter command. + num_passes=1, # int: number of moves forward and reverse between start angle and stop angle + ): + command = "startScanEx2" if start_angle is None: - start_angle=self.omega.get() - return self.exporter.cmd(command, [frame_number, num_images, start_angle, scan_range, exposure_time, num_passes]) - - def vector_scan(self, - start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=10, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=1, # double: exposure time (sec) to control shutter command. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - stop_y=None, # double: PhiY axis position at the end of the exposure. - stop_z=None, # double: PhiZ axis position at the end of the exposure. - stop_cx=None, # double: CentringX axis position at the end of the exposure. - stop_cy=None, # double: CentringY axis position at the end of the exposure. - ): - command = 'startScan4DEx' + start_angle = self.omega.get() + return self.exporter.cmd( + command, + [ + frame_number, + num_images, + start_angle, + scan_range, + exposure_time, + num_passes, + ], + ) + + def vector_scan( + self, + start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + scan_range=10, # double: omega relative move angle (deg) before closing the shutter. + exposure_time=1, # double: exposure time (sec) to control shutter command. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + stop_y=None, # double: PhiY axis position at the end of the exposure. + stop_z=None, # double: PhiZ axis position at the end of the exposure. + stop_cx=None, # double: CentringX axis position at the end of the exposure. + stop_cy=None, # double: CentringY axis position at the end of the exposure. + ): + command = "startScan4DEx" if start_angle is None: start_angle = self.omega.val() if start_y is None: @@ -244,29 +275,40 @@ def vector_scan(self, # beginning of the deceleration. # Inputs names: "start_angle", "scan_range", "exposure_time", "start_y", "start_z", "start_cx", # "start_cy", "stop_y", "stop_z", "stop_cx", "stop_cy" - param_list = [start_angle, scan_range, exposure_time, - start_y, start_z, start_cx, start_cy, - stop_y, stop_z, stop_cx, stop_cy] + param_list = [ + start_angle, + scan_range, + exposure_time, + start_y, + start_z, + start_cx, + start_cy, + stop_y, + stop_z, + stop_cx, + stop_cy, + ] return self.exporter.cmd(command, param_list) - def raster_scan(self, - omega_range=0, # double: omega relative move angle (deg) before closing the shutter. - line_range=0.1, # double: horizontal range of the grid (mm). - total_uturn_range=0.1, # double: vertical range of the grid (mm). - start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - number_of_lines=5, # int: number of frames on the vertical range. - frames_per_line=5, # int: number of frames on the horizontal range. - exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given - invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_centring_table=True, # boolean: true to use the centring table to do the pitch movements. - use_fast_mesh_scans=True # boolean: true to use the fast raster scan if available (power PMAC). - ): - - command = 'startRasterScanEx' + def raster_scan( + self, + omega_range=0, # double: omega relative move angle (deg) before closing the shutter. + line_range=0.1, # double: horizontal range of the grid (mm). + total_uturn_range=0.1, # double: vertical range of the grid (mm). + start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. + start_y=None, # double: PhiY axis position at the beginning of the exposure. + start_z=None, # double: PhiZ axis position at the beginning of the exposure. + start_cx=None, # double: CentringX axis position at the beginning of the exposure. + start_cy=None, # double: CentringY axis position at the beginning of the exposure. + number_of_lines=5, # int: number of frames on the vertical range. + frames_per_line=5, # int: number of frames on the horizontal range. + exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given + invert_direction=True, # boolean: true to enable passes in the reverse direction. + use_centring_table=True, # boolean: true to use the centring table to do the pitch movements. + use_fast_mesh_scans=True, # boolean: true to use the fast raster scan if available (power PMAC). + ): + + command = "startRasterScanEx" if start_omega is None: start_omega = self.omega.val() if start_y is None: @@ -285,44 +327,66 @@ def raster_scan(self, # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", # "invert_direction", "use_centring_table", "use_fast_mesh_scans" - param_list = [omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, - start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, - invert_direction, use_centring_table, use_fast_mesh_scans] + param_list = [ + omega_range, + line_range, + total_uturn_range, + start_omega, + start_y, + start_z, + start_cx, + start_cy, + number_of_lines, + frames_per_line, + exposure_time, + invert_direction, + use_centring_table, + use_fast_mesh_scans, + ] return self.exporter.cmd(command, param_list) - + + class MD2ApertureDevice(Device): # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" - current_index = Cpt(EpicsSignal, 'CurrentApertureDiameterIndex', name='current_index') - diameters = Cpt(EpicsSignalRO, 'ApertureDiameters', name='diameters') + current_index = Cpt( + EpicsSignal, "CurrentApertureDiameterIndex", name="current_index" + ) + diameters = Cpt(EpicsSignalRO, "ApertureDiameters", name="diameters") def get_diameter_list(self): # must format list for GUI # also, can't currently get from PV return self.diameters.get() - + def set_diameter(self, diameter): if diameter in self.get_diameter_list(): self.current_index.put(self.get_diameter_list().index(diameter)) + class ShutterDevice(Device): - control = Cpt(EpicsSignal, '{MD2}:FastShutterIsOpen', name='control') # PV to send control signal - pos_opn = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Opn-I', name='pos_opn') - pos_cls = Cpt(EpicsSignalRO, '{Gon:1-Sht}Pos:Cls-I', name='pos_cls') + control = Cpt( + EpicsSignal, "{MD2}:FastShutterIsOpen", name="control" + ) # PV to send control signal + pos_opn = Cpt(EpicsSignalRO, "{Gon:1-Sht}Pos:Opn-I", name="pos_opn") + pos_cls = Cpt(EpicsSignalRO, "{Gon:1-Sht}Pos:Cls-I", name="pos_cls") def is_open(self): - return self.control.get() == 1 #self.pos_opn.get() + return self.control.get() == 1 # self.pos_opn.get() def open_shutter(self): - self.control.set(1)#self.pos_opn.get()) iocs are down, so just setting it to 1 + self.control.set( + 1 + ) # self.pos_opn.get()) iocs are down, so just setting it to 1 def close_shutter(self): - self.control.set(0)#self.pos_cls.get()) + self.control.set(0) # self.pos_cls.get()) + class CameraDevice(Device): # MD2 camera has the following attributes: # CoaxCamScaleX # CoaxCamScaleY # CoaxialCameraZoomValue - scale_x = Cpt(EpicsSignalRO, 'CoaxCamScaleX', name='scale_x') - scale_y = Cpt(EpicsSignalRO, 'CoaxCamScaleY', name='scale_y') - zoom = Cpt(EpicsSignal, 'CoaxialCameraZoomValue', name='zoom') + scale_x = Cpt(EpicsSignalRO, "CoaxCamScaleX", name="scale_x") + scale_y = Cpt(EpicsSignalRO, "CoaxCamScaleY", name="scale_y") + zoom = Cpt(EpicsSignal, "CoaxialCameraZoomValue", name="zoom") diff --git a/start_bs.py b/start_bs.py index d5eb8a95..df5ac1ac 100755 --- a/start_bs.py +++ b/start_bs.py @@ -8,7 +8,7 @@ import os from mxtools.governor import _make_governors from ophyd.signal import EpicsSignalBase -from devices import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice +from mxbluesky.devices.md2 import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, TopAlignerFast, TopAlignerSlow, GoniometerStack) From e3bbf06985534a1692fb2ba504862dc65cb18e65 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 30 Apr 2024 11:19:40 -0400 Subject: [PATCH 215/234] Removed setBlConfigs specific to NYX --- daq_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/daq_utils.py b/daq_utils.py index 00b18c78..5ee3e99e 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -53,9 +53,6 @@ def setBlConfig(param, value, beamline=beamline): def init_environment(): global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle,exporter_enabled - setBlConfig("highMagPixX", "1224") - setBlConfig("highMagPixY", "1024") - setBlConfig("highMagCamURL", "http://10.67.147.26:3908/video_feed2") owner = getpass.getuser() primaryDewarName = getBlConfig("primaryDewarName") db_lib.setPrimaryDewarName(primaryDewarName) From 1d184ebdfe86d79e9091871a9ac7183a00d874b1 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 30 Apr 2024 19:33:31 -0400 Subject: [PATCH 216/234] Added nyx specific imports --- beamline_support.py | 2 +- daq_utils.py | 3 ++- gon_lib.py | 4 +++- start_bs.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/beamline_support.py b/beamline_support.py index eb8e2cf1..0d8a926d 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -4,7 +4,6 @@ import ophyd from ophyd import EpicsMotor from ophyd import EpicsScaler -from mxbluesky.devices.md2 import MD2Positioner import time import epics import os @@ -324,6 +323,7 @@ def init_motors(): for key in list(motor_dict.keys()): if beamline_designation == "XF:19ID" and key in md2_motors: + from mxbluesky.devices.md2 import MD2Positioner motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) else: motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) diff --git a/daq_utils.py b/daq_utils.py index 5ee3e99e..ddf69058 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -71,7 +71,8 @@ def init_environment(): highMagPixY = float(getBlConfig("highMagPixY")) screenPixX = float(getBlConfig("screenPixX")) screenPixY = float(getBlConfig("screenPixY")) - exporter_enabled = bool(getBlConfig("exporterEnabled")) + if beamline == 'nyx': + exporter_enabled = bool(getBlConfig("exporterEnabled")) try: unitScaling = float(getBlConfig("unitScaling")) diff --git a/gon_lib.py b/gon_lib.py index 70f58d85..a42d4540 100755 --- a/gon_lib.py +++ b/gon_lib.py @@ -2,8 +2,10 @@ import beamline_lib import beamline_support from beamline_support import getPvValFromDescriptor as getPvDesc, setPvValFromDescriptor as setPvDesc -from start_bs import back_light, back_light_range, md2 import logging +import daq_utils +if daq_utils.beamline == 'nyx': + from start_bs import back_light, back_light_range, md2 logger = logging.getLogger(__name__) BACK_LIGHT_STEP = 0.05 # percent of intensity range diff --git a/start_bs.py b/start_bs.py index df5ac1ac..945dd50f 100755 --- a/start_bs.py +++ b/start_bs.py @@ -8,7 +8,6 @@ import os from mxtools.governor import _make_governors from ophyd.signal import EpicsSignalBase -from mxbluesky.devices.md2 import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, TopAlignerFast, TopAlignerSlow, GoniometerStack) @@ -180,6 +179,7 @@ class SampleXYZ(Device): import setenergy_lsdc elif beamline=="nyx": + from mxbluesky.devices.md2 import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] From bed0867bf3a9f05816a3e6719401d39d6f6b9370 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Tue, 30 Apr 2024 19:46:49 -0400 Subject: [PATCH 217/234] Combined gonio ophyd devices --- gui/control_main.py | 11 ++++++++--- mxbluesky/devices/__init__.py | 37 +++++++++++++++++++++++++++++++---- mxbluesky/devices/generic.py | 11 +++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 522255ea..a5a5a8d6 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -22,9 +22,12 @@ from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication -from mxbluesky.devices.md2 import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice import daq_utils +if daq_utils.beamline == 'nyx': + from mxbluesky.devices.md2 import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice +else: + from mxbluesky.devices.generic import GoniometerStack as GonioDevice import db_lib import lsdcOlog from config_params import ( @@ -5092,8 +5095,10 @@ def initOphyd(self): self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") - else: - pass + elif daq_utils.beamline == "amx": + self.gon = GonioDevice("XF:17IDB-ES:AMX{Gon:1", name="gonio") + elif daq_utils.beamline == "fmx": + self.gon = GonioDevice("XF:17IDC-ES:FMX{Gon:1", name="gonio") def initUI(self): self.tabs = QtWidgets.QTabWidget() diff --git a/mxbluesky/devices/__init__.py b/mxbluesky/devices/__init__.py index 5a8e42c7..191b3f96 100644 --- a/mxbluesky/devices/__init__.py +++ b/mxbluesky/devices/__init__.py @@ -1,4 +1,33 @@ -from .auto_center import * -from .generic import * -from .top_align import * -from .zebra import * +from functools import wraps +from ophyd import Device +try: + from .auto_center import * + from .top_align import * + from .zebra import * +except: + pass + + +def standardize_readback(cls): + original_init = cls.__init__ + + @wraps(original_init) + def new_init(self, *args, **kwargs): + methods_to_extend = {"readback": "user_readback", + "setpoint": "user_setpoint", + "val": "user_readback.get"} + original_init(self, *args, **kwargs) + for attr_name in self.component_names: + comp = getattr(self, attr_name) + if not isinstance(comp, Device): + continue + + for new_method_name, old_method_name in methods_to_extend.items(): + if not hasattr(comp, new_method_name): + if hasattr(comp, old_method_name): + setattr(comp, new_method_name, getattr(comp, old_method_name)) + + cls.__init__ = new_init + return cls + +from .generic import * \ No newline at end of file diff --git a/mxbluesky/devices/generic.py b/mxbluesky/devices/generic.py index 2e458240..9c0bae4d 100644 --- a/mxbluesky/devices/generic.py +++ b/mxbluesky/devices/generic.py @@ -23,3 +23,14 @@ class GoniometerStack(Device): o = Cpt(EpicsMotor, "-Ax:O}Mtr") py = Cpt(EpicsMotor, "-Ax:PY}Mtr") pz = Cpt(EpicsMotor, "-Ax:PZ}Mtr") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Renaming to match MD2 GonioDevice + self.x = self.gx + self.cx = self.gx + self.y = self.py + self.cy = self.py + self.z = self.pz + self.cz = self.pz + self.omega = self.o \ No newline at end of file From 7d66b966cc62eaf69a0255105abca99bead37fd3 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 1 May 2024 08:41:54 -0400 Subject: [PATCH 218/234] replaced getMD2BeamCenter with getBeamCenter which will return the correct value --- gui/control_main.py | 88 +++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index a5a5a8d6..38cdc664 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -444,10 +444,6 @@ def createSampleTab(self): ) if daq_utils.beamline == "fmx": self.osc_end_ledit.textChanged.connect(self.calcLifetimeCB) - #hBoxColParams1.addWidget(colStartLabel) - #hBoxColParams1.addWidget(self.osc_start_ledit) - #hBoxColParams1.addWidget(self.colEndLabel) - #hBoxColParams1.addWidget(self.osc_end_ledit) hBoxColParams2 = QtWidgets.QHBoxLayout() colRangeLabel = QtWidgets.QLabel("Oscillation Width:") colRangeLabel.setFixedWidth(140) @@ -1116,8 +1112,8 @@ def createSampleTab(self): self.centerMarker.setFont(font) self.scene.addItem(self.centerMarker) self.centerMarker.setPos( - self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX, - self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY, + self.getBeamCenterX() - self.centerMarkerCharOffsetX, + self.getBeamCenterY() - self.centerMarkerCharOffsetY, ) self.zoomRadioGroup = QtWidgets.QButtonGroup() self.zoom1Radio = QtWidgets.QRadioButton("Mag1") @@ -1760,15 +1756,15 @@ def flushBuffer(self, vidStream): def zoomLevelComboActivatedCB(self, identifier): self.camera.zoom.put(identifier) - self.centerMarker.setPos(self.getMD2BeamCenterX()-self.centerMarkerCharOffsetX, self.getMD2BeamCenterY()-self.centerMarkerCharOffsetY) + self.centerMarker.setPos(self.getBeamCenterX()-self.centerMarkerCharOffsetX, self.getBeamCenterY()-self.centerMarkerCharOffsetY) #self.flushBuffer(self.capture) #self.capture.release() #self.capture = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) def zoomLevelToggledCB(self, identifier): fov = {} - zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX - zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY + zoomedCursorX = self.getBeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): self.flushBuffer(self.captureLowMagZoom) self.capture = self.captureLowMagZoom @@ -1776,18 +1772,18 @@ def zoomLevelToggledCB(self, identifier): fov["y"] = daq_utils.lowMagFOVy / 2.0 unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY - if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): + if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): + if unzoomedCursorY * 2.0 < self.getBeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - self.getMD2BeamCenterX() - > self.getMD2BeamCenterX() / 2 + unzoomedCursorX - self.getBeamCenterX() + > self.getBeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - self.getMD2BeamCenterY() - > self.getMD2BeamCenterY() / 2 + unzoomedCursorY - self.getBeamCenterY() + > self.getBeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -1835,18 +1831,18 @@ def zoomLevelToggledCB(self, identifier): unzoomedCursorY = ( self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY ) - if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): + if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): + if unzoomedCursorY * 2.0 < self.getBeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - self.getMD2BeamCenterX() - > self.getMD2BeamCenterX() / 2 + unzoomedCursorX - self.getBeamCenterX() + > self.getBeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - self.getMD2BeamCenterY() - > self.getMD2BeamCenterY() / 2 + unzoomedCursorY - self.getBeamCenterY() + > self.getBeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -2007,23 +2003,23 @@ def processROIChange(self, posRBV, ID): pass def processLowMagCursorChange(self, posRBV, ID): - zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX - zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY + zoomedCursorX = self.getBeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): # lowmagzoom unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY - if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): + if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): + if unzoomedCursorY * 2.0 < self.getBeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - self.getMD2BeamCenterX() - > self.getMD2BeamCenterX() / 2 + unzoomedCursorX - self.getBeamCenterX() + > self.getBeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - self.getMD2BeamCenterY() - > self.getMD2BeamCenterY() / 2 + unzoomedCursorY - self.getBeamCenterY() + > self.getBeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -2058,8 +2054,8 @@ def processLowMagCursorChange(self, posRBV, ID): ) def processHighMagCursorChange(self, posRBV, ID): - zoomedCursorX = self.getMD2BeamCenterX() - self.centerMarkerCharOffsetX - zoomedCursorY = self.getMD2BeamCenterY() - self.centerMarkerCharOffsetY + zoomedCursorX = self.getBeamCenterX() - self.centerMarkerCharOffsetX + zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom4Radio.isChecked(): # highmagzoom unzoomedCursorX = ( self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX @@ -2067,18 +2063,18 @@ def processHighMagCursorChange(self, posRBV, ID): unzoomedCursorY = ( self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY ) - if unzoomedCursorX * 2.0 < self.getMD2BeamCenterX(): + if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 - if unzoomedCursorY * 2.0 < self.getMD2BeamCenterY(): + if unzoomedCursorY * 2.0 < self.getBeamCenterY(): zoomedCursorY = unzoomedCursorY * 2.0 if ( - unzoomedCursorX - self.getMD2BeamCenterX() - > self.getMD2BeamCenterX() / 2 + unzoomedCursorX - self.getBeamCenterX() + > self.getBeamCenterX() / 2 ): zoomedCursorX = (unzoomedCursorX * 2.0) - daq_utils.screenPixX if ( - unzoomedCursorY - self.getMD2BeamCenterY() - > self.getMD2BeamCenterY() / 2 + unzoomedCursorY - self.getBeamCenterY() + > self.getBeamCenterY() / 2 ): zoomedCursorY = (unzoomedCursorY * 2.0) - daq_utils.screenPixY self.centerMarker.setPos(zoomedCursorX, zoomedCursorY) @@ -3507,11 +3503,17 @@ def getMD2ImageYRatio(self): lsdc_img_height = daq_utils.screenPixY return float(md2_img_height) / float(lsdc_img_height) - def getMD2BeamCenterX(self): - return self.md2.center_pixel_x.get() / self.getMD2ImageXRatio() + def getBeamCenterX(self): + if daq_utils.beamline == 'nyx': + return self.md2.center_pixel_x.get() / self.getMD2ImageXRatio() + else: + return daq_utils.screenPixCenterX - def getMD2BeamCenterY(self): - return self.md2.center_pixel_y.get() / self.getMD2ImageYRatio() + def getBeamCenterY(self): + if daq_utils.beamline == 'nyx': + return self.md2.center_pixel_y.get() / self.getMD2ImageYRatio() + else: + return daq_utils.screenPixCenterY def definePolyRaster( self, raster_w, raster_h, stepsizeXPix, stepsizeYPix, point_x, point_y, stepsize @@ -3848,10 +3850,10 @@ def pixelSelect(self, event): self.drawInteractiveRasterCB() return fov = self.getCurrentFOV() - correctedC2C_x = self.getMD2BeamCenterX() + ( + correctedC2C_x = self.getBeamCenterX() + ( x_click - (self.centerMarker.x() - self.centerMarkerCharOffsetX) - 20 ) - correctedC2C_y = self.getMD2BeamCenterY() + ( + correctedC2C_y = self.getBeamCenterY() + ( y_click - (self.centerMarker.y() - self.centerMarkerCharOffsetY) - 40 ) From 412bc35364cfb162c9148f8caef6eeb0c20379bb Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 1 May 2024 09:12:54 -0400 Subject: [PATCH 219/234] Fixed pixel to micron conversions for all beamlines --- gui/control_main.py | 53 ++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 38cdc664..3038fdc7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3469,29 +3469,48 @@ def getCurrentFOV(self): return fov def screenXPixels2microns(self, pixels): - img_scale_factor = self.getMD2ImageXRatio() - pixels_per_mm = 1 / self.camera.scale_x.get() - pixels_per_micron = pixels_per_mm / 1000.0 - return float(pixels * img_scale_factor) / pixels_per_micron - print(f"pixels per micron = {pixels_per_micron}") + if daq_utils.beamline == 'nyx': + img_scale_factor = self.getMD2ImageXRatio() + pixels_per_mm = 1 / self.camera.scale_x.get() + pixels_per_micron = pixels_per_mm / 1000.0 + return float(pixels * img_scale_factor) / pixels_per_micron + else: + fov = self.getCurrentFOV() + fovX = fov["x"] + return float(pixels) * (fovX / daq_utils.screenPixX) def screenYPixels2microns(self, pixels): - pixels_per_mm = 1 / self.camera.scale_y.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageYRatio() - return float(pixels * img_scale_factor) / pixels_per_micron + if daq_utils.beamline == 'nyx': + pixels_per_mm = 1 / self.camera.scale_y.get() + pixels_per_micron = pixels_per_mm / 1000.0 + img_scale_factor = self.getMD2ImageYRatio() + return float(pixels * img_scale_factor) / pixels_per_micron + else: + fov = self.getCurrentFOV() + fovY = fov["y"] + return float(pixels) * (fovY / daq_utils.screenPixY) def screenXmicrons2pixels(self, microns): - pixels_per_mm = 1 / self.camera.scale_x.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageXRatio() - return float(microns * pixels_per_micron) / img_scale_factor + if daq_utils.beamline == 'nyx': + pixels_per_mm = 1 / self.camera.scale_x.get() + pixels_per_micron = pixels_per_mm / 1000.0 + img_scale_factor = self.getMD2ImageXRatio() + return float(microns * pixels_per_micron) / img_scale_factor + else: + fov = self.getCurrentFOV() + fovX = fov["x"] + return int(round(microns * (daq_utils.screenPixX / fovX))) def screenYmicrons2pixels(self, microns): - pixels_per_mm = 1 / self.camera.scale_y.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageYRatio() - return float(microns * pixels_per_micron) / img_scale_factor + if daq_utils.beamline == 'nyx': + pixels_per_mm = 1 / self.camera.scale_y.get() + pixels_per_micron = pixels_per_mm / 1000.0 + img_scale_factor = self.getMD2ImageYRatio() + return float(microns * pixels_per_micron) / img_scale_factor + else: + fov = self.getCurrentFOV() + fovY = fov["y"] + return int(round(microns * (daq_utils.screenPixY / fovY))) def getMD2ImageXRatio(self): md2_img_width = daq_utils.highMagPixX From 8c2cbf05362db83cb560766283e0234f9c0ed405 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 1 May 2024 09:16:08 -0400 Subject: [PATCH 220/234] Added nyx check in pixelSelect --- gui/control_main.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 3038fdc7..290af274 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -3834,11 +3834,12 @@ def pixelSelect(self, event): This ideally gives feedback on wether the MD2 is in the rotation portion of the three click centering ''' - state = self.md2.exporter.read('OmegaState') - if state != 'Ready': - logger.info('waiting for motor rotation') - logger.info('Click not registered') - return + if daq_utils.beamline == 'nyx': + state = self.md2.exporter.read('OmegaState') + if state != 'Ready': + logger.info('waiting for motor rotation') + logger.info('Click not registered') + return if self.vidActionDefineCenterRadio.isChecked(): self.vidActionC2CRadio.setChecked( True From 6a55ef6263d36b6ddddfa2e497c8eed5f6104810 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 1 May 2024 19:15:21 -0400 Subject: [PATCH 221/234] Multiple fixes: - Replaced samplexyz with common gonio device - Cleaned up edits to plans not used by nyx - Fixed NYX specific code --- config_params.py | 1 + daq_lib.py | 2 +- daq_macros.py | 121 ++++++++++++++--------------------- gui/control_main.py | 15 +---- mxbluesky/devices/generic.py | 4 +- 5 files changed, 55 insertions(+), 88 deletions(-) diff --git a/config_params.py b/config_params.py index 95d58a6f..d5ce421e 100644 --- a/config_params.py +++ b/config_params.py @@ -134,3 +134,4 @@ class OnMountAvailOptions(Enum): "S": ["V0", "H0"], "L": ["V1", "H1"] } +OPHYD_COLLECTIONS = {"amx": False, "fmx": False, "nyx": False} diff --git a/daq_lib.py b/daq_lib.py index 372bbbd7..af1fc149 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -704,7 +704,7 @@ def collect_detector_seq_hw(sweep_start,range_degrees,image_width,exposure_perio logger.info("collect %f degrees for %f seconds %d images exposure_period = %f exposure_time = %f" % (range_degrees,range_seconds,number_of_images,exposure_period,exposure_time)) - if(getBlConfig("ophydCollections") == True): + if OPHYD_COLLECTIONS[daq_utils.beamline]: logger.info("ophyd collections enabled") if (protocol == "standard"): RE(daq_macros.standard_plan_wrapped(currentRequest)) diff --git a/daq_macros.py b/daq_macros.py index 643dc78d..debb1eb4 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -114,9 +114,9 @@ def move_omega(omega, relative=True): """Moves omega by a certain amount""" if gov_robot.state.get() == "SA": if relative: - RE(bps.mvr(samplexyz.omega, omega)) + RE(bps.mvr(gonio.omega, omega)) else: - RE(bps.mv(samplexyz.omega, omega)) + RE(bps.mv(gonio.omega, omega)) def changeImageCenterLowMag(x,y,czoom): zoom = int(czoom) @@ -403,7 +403,6 @@ def autoRasterLoop(currentRequest): def autoRasterLoopOld(currentRequest): global autoRasterFlag - logger.info('entering autoRasterLoop') gov_status = gov_lib.setGovRobot(gov_robot, 'SA') if not gov_status.success: return 0 @@ -1001,12 +1000,12 @@ def runDozorThread(directory, ID of raster collection """ global rasterRowResultsList,processedRasterRowCount - - time.sleep(10.0) #allow for file writing - - #node = getNodeName("spot", rowIndex, 8) - node = "titania-cpu00"+str((rowIndex%4)+1) - logger.info(f"distributing row {rowIndex} to {node}") + file_writing_delay = 0.5 + node = getNodeName("spot", rowIndex, 8) + if daq_utils.beamline == 'nyx': + file_writing_delay = 10 + node = "titania-cpu00"+str((rowIndex%4)+1) + time.sleep(file_writing_delay) #allow for file writing if (seqNum>-1): #eiger dozorRowDir = makeDozorInputFile(directory, @@ -1730,7 +1729,7 @@ def snakeRasterNormal(rasterReqID,grain=""): if not procFlag: #must go to known position to account for windup dist. logger.info("moving to raster start") - samplexyz.put(rasterStartX, rasterStartY, rasterStartZ, omega) + gonio.put(rasterStartX, rasterStartY, rasterStartZ, omega) logger.info("done moving to raster start") if (procFlag): @@ -2043,33 +2042,35 @@ def snakeRasterBluesky(rasterReqID, grain=""): total_exposure_time = exptimePerCell*totalImages detDist /= 1000 # TODO find a way to standardize detector distance settings - raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) if raster_flyer.detector.cam.armed.get() == 1: daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' 'should check the most recent collection to determine if it was successful. Cancelling' 'this collection, retry when ready.') - raster_flyer.detector.cam.acquire.put(0) logger.warning("Detector was in the armed state prior to this attempted collection.") return 0 - raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix, + start_time = time.time() + arm_status = raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix, data_directory_name=data_directory_name, file_number_start=file_number_start, x_beam=xbeam, y_beam=ybeam, wavelength=wave, det_distance_m=detDist, - num_images_per_file=numsteps) # rasterDef['numCells']) TODO: try to get all images in one file - #raster_flyer.detector.stage() + num_images_per_file=numsteps) + govStatus = gov_lib.setGovRobot(gov_robot, "DA") + arm_status.wait() + logger.info(f"Governor move to DA and synchronous arming took {time.time() - start_time} seconds.") + if govStatus.exception(): + logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}") + return + raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name) + raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] - yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row - logger.info(f'starting new row: {row_index}') - zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index) - vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} - yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) + xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(row, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, row_index) + vector = {'x': (xMotAbsoluteMove, xEnd), 'y': (yMotAbsoluteMove, yEnd), 'z': (zMotAbsoluteMove, zEnd)} yield from zebraDaqRasterBluesky(raster_flyer, omega, numsteps, img_width_per_cell * numsteps, img_width_per_cell, exptimePerCell, rasterFilePrefix, data_directory_name, file_number_start, row_index, vector) - #raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row - yield from bps.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4 - + raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row + time.sleep(0.2) # necessary for reliable row processing - see comment in commit 6793f4 # processing - if (procFlag): + if (procFlag): if (daq_utils.detector_id == "EIGER-16"): seqNum = int(raster_flyer.detector.file.sequence_id.get()) else: @@ -2091,7 +2092,6 @@ def snakeRasterBluesky(rasterReqID, grain=""): initiate transitions here allows for GUI sample/heat map image to update after moving to known position""" logger.debug(f'lastOnSample(): {lastOnSample()} autoRasterFlag: {autoRasterFlag}') - yield from bps.sleep(3) #waiting for detector to not lose row if (lastOnSample() and not autoRasterFlag): govStatus = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) if govStatus.exception(): @@ -2115,16 +2115,15 @@ def snakeRasterBluesky(rasterReqID, grain=""): #data acquisition is finished, now processing and sample positioning if not procFlag: # no, no processing. just move to raster start #must go to known position to account for windup dist. - logger.info(f" no processing! moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}") - yield from bps.mv(samplexyz.x, rasterStartX) - yield from bps.mv(samplexyz.y, rasterStartY) - yield from bps.mv(samplexyz.z, rasterStartZ) - yield from bps.mv(samplexyz.omega, omega) + logger.info("moving to raster start") + yield from bps.mv(gonio.x, rasterStartX) + yield from bps.mv(gonio.y, rasterStartY) + yield from bps.mv(gonio.z, rasterStartZ) + yield from bps.mv(gonio.omega, omega) logger.info("done moving to raster start") else: # yes, do row processing if daq_lib.abort_flag != 1: - print("processing rows") [thread.join(timeout=120) for thread in spotFindThreadList] else: logger.info("raster aborted, do not wait for spotfind threads") @@ -2136,13 +2135,8 @@ def snakeRasterBluesky(rasterReqID, grain=""): if (parentReqProtocol == "multiColQ"): multiColThreshold = parentReqObj["diffCutoff"] else: - multiColThreshold = reqObj["diffCutoff"] - # gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) - logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}") - yield from bps.mv(samplexyz.x, rasterStartX) - yield from bps.mv(samplexyz.y, rasterStartY) - yield from bps.mv(samplexyz.z, rasterStartZ) - yield from bps.mv(samplexyz.omega, omega) + multiColThreshold = reqObj["diffCutoff"] + gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) else: try: # go to start omega for faster heat map display @@ -2150,10 +2144,10 @@ def snakeRasterBluesky(rasterReqID, grain=""): except ValueError: #must go to known position to account for windup dist. logger.info("moving to raster start because of value error in gotoMaxRaster") - yield from bps.mv(samplexyz.x, rasterStartX) - yield from bps.mv(samplexyz.y, rasterStartY) - yield from bps.mv(samplexyz.z, rasterStartZ) - yield from bps.mv(samplexyz.omega, omega) + yield from bps.mv(gonio.x, rasterStartX) + yield from bps.mv(gonio.y, rasterStartY) + yield from bps.mv(gonio.z, rasterStartZ) + yield from bps.mv(gonio.omega, omega) logger.info("done moving to raster start") """change request status so that GUI only fills heat map when @@ -2492,11 +2486,11 @@ def gotoMaxRaster(rasterResult,multiColThreshold=None,**kwargs): if max_index: x, y, z = run_auto_raster(max_index, score_vals, scoreOption, cellResults, rasterMap, **kwargs) if 'omega' in kwargs: - beamline_lib.mvaDescriptor("sampleX",x/1000, - "sampleY",y/1000, - "sampleZ",z/1000, + beamline_lib.mvaDescriptor("sampleX",x, + "sampleY",y, + "sampleZ",z, "omega",kwargs['omega']) - else: beamline_lib.mvaDescriptor("sampleX",x/1000,"sampleY",y/1000,"sampleZ",z/1000) + else: beamline_lib.mvaDescriptor("sampleX",x,"sampleY",y,"sampleZ",z) if (autoVectorFlag): #if we found a hotspot, then look again at cellResults for coarse vector start and end run_auto_vector(score_val, cellResults, scoreOption, rasterMap) @@ -3488,20 +3482,17 @@ def clean_up_files(pic_prefix, output_file): def loop_center_xrec(): global face_on - print('entering daq_macros.loop_center_xrec') daq_lib.abort_flag = 0 pic_prefix = "findloop" output_file = 'xrec_result.txt' - print('clean up files') clean_up_files(pic_prefix, output_file) - #TODO: if daq_utils.beamline=='nyx': - print('post clean') - xrec_no_zebra(0) - print('post no zebra') - #else: - # zebraCamDaq(0,360,40,.4,pic_prefix,os.getcwd(),0) - #zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) + if daq_utils.beamline=='nyx': + print('post clean') + xrec_no_zebra(0) + print('post no zebra') + else: + zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) comm_s = f'xrec {os.environ["CONFIGDIR"]}/xrec_360_40Fast.txt {output_file}' logger.info(comm_s) try: @@ -3557,9 +3548,7 @@ def loop_center_xrec(): def xrec_no_zebra(angle_start): print(f'xrec_no_zebra{angle_start}') beamline_lib.mvaDescriptor("omega", angle_start) - #yield from bps.mv(samplexyz.omega, angle_start) for omega_target in range (angle_start, angle_start+360, 40): - #yield from bps.mv(samplexyz.omega, omega_target) beamline_lib.mvaDescriptor("omega", omega_target) logger.info(f'taking image at {omega_target}') timeout = 5 @@ -3861,8 +3850,6 @@ def rasterDaq(rasterReqID): #logger.info(f"req_obj: {reqObj}") i = 0 xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(rows[i], stepsize, (start_omega*0), start_x, start_y, start_z, i) - # vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)} - # yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05) stepsize /= 1000 # MD2 wants mm logger.info(f"move calculations: {xMotAbsoluteMove}, {xEnd}, {yMotAbsoluteMove}, {yEnd}, {zMotAbsoluteMove}, {zEnd}") line_range = stepsize * numsteps @@ -4038,20 +4025,8 @@ def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposur 'change_state':changeState, 'transmission':vector_params["transmission"], 'data_path':data_path} start_time = time.time() - flyer.detector_arm(**required_parameters) - - def armed_callback(value, old_value, **kwargs): - if old_value == 0 and value == 1: - return True - return False - - arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) - - flyer.detector.cam.acquire.put(1) - + arm_status = flyer.detector_arm(**required_parameters) govStatus = gov_lib.setGovRobot(gov_robot, "DA") - time.sleep(0.5) - govStatus.wait() arm_status.wait() logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") if govStatus.exception(): @@ -4106,7 +4081,7 @@ def zebraDaqRasterBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, e row_index=row_index, transmission=1, protocol="raster") yield from bp.fly([raster_flyer]) - logger.info(f"vector Done, zebra arm status: {raster_flyer.zebra.pc.arm.output}") + logger.info("vector Done") logger.info("zebraDaqRasterBluesky Done") def zebraDaq(vector_program,angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3,changeState=True): #scan encoder 0=x, 1=y,2=z,3=omega diff --git a/gui/control_main.py b/gui/control_main.py index 290af274..c156c84d 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -595,17 +595,6 @@ def createSampleTab(self): self.energy_ledit.returnPressed.connect(self.moveEnergyMaxDeltaCB) moveEnergyButton = QtWidgets.QPushButton("Move Energy") moveEnergyButton.clicked.connect(self.moveEnergyCB) - #hBoxColParams3.addWidget(colEnergyLabel) - #hBoxColParams3.addWidget(self.energyReadback) - #hBoxColParams3.addWidget(energySPLabel) - #hBoxColParams3.addWidget(self.energy_ledit) - #hBoxColParams22.addWidget(colTransmissionLabel) - #hBoxColParams22.addWidget(self.transmissionReadback_ledit) - #hBoxColParams22.addWidget(transmisionSPLabel) - #hBoxColParams22.addWidget(self.transmission_ledit) - #hBoxColParams22.insertSpacing(5, 100) - #hBoxColParams22.addWidget(beamsizeLabel) - #hBoxColParams22.addWidget(self.beamsizeComboBox) hBoxColParams4 = QtWidgets.QHBoxLayout() colBeamWLabel = QtWidgets.QLabel("Beam Width:") colBeamWLabel.setFixedWidth(140) @@ -618,7 +607,9 @@ def createSampleTab(self): colBeamHLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamHeight_ledit = QtWidgets.QLineEdit() self.beamHeight_ledit.setFixedWidth(60) - self.beamHeight_ledit.setText(getBlConfig("screen_default_beamHeight")) + if daq_utils.beamline == 'nyx': + self.beamWidth_ledit.setText(getBlConfig("screen_default_beamWidth")) + self.beamHeight_ledit.setText(getBlConfig("screen_default_beamHeight")) hBoxColParams4.addWidget(colBeamWLabel) hBoxColParams4.addWidget(self.beamWidth_ledit) hBoxColParams4.addWidget(colBeamHLabel) diff --git a/mxbluesky/devices/generic.py b/mxbluesky/devices/generic.py index 9c0bae4d..e9c9cbdd 100644 --- a/mxbluesky/devices/generic.py +++ b/mxbluesky/devices/generic.py @@ -1,6 +1,6 @@ from ophyd import Component as Cpt from ophyd import Device, EpicsMotor, EpicsSignal - +from mxbluesky.devices import standardize_readback class WorkPositions(Device): gx = Cpt(EpicsSignal, "{Gov:Robot-Dev:gx}Pos:Work-Pos") @@ -15,7 +15,7 @@ class MountPositions(Device): pz = Cpt(EpicsSignal, "{Gov:Robot-Dev:gpz}Pos:Mount-Pos") o = Cpt(EpicsSignal, "{Gov:Robot-Dev:go}Pos:Mount-Pos") - +@standardize_readback class GoniometerStack(Device): gx = Cpt(EpicsMotor, "-Ax:GX}Mtr") gy = Cpt(EpicsMotor, "-Ax:GY}Mtr") From 30be6b21482140deac7d97b97dd3ae3b3167aee1 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 1 May 2024 19:25:28 -0400 Subject: [PATCH 222/234] Fixed NYX specific line --- gui/control_main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index c156c84d..cbcb7ffb 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -601,7 +601,6 @@ def createSampleTab(self): colBeamWLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamWidth_ledit = QtWidgets.QLineEdit() self.beamWidth_ledit.setFixedWidth(60) - self.beamWidth_ledit.setText(getBlConfig("screen_default_beamWidth")) colBeamHLabel = QtWidgets.QLabel("Beam Height:") colBeamHLabel.setFixedWidth(140) colBeamHLabel.setAlignment(QtCore.Qt.AlignCenter) From c167573e2b068028a69e697dc6d0db9c741ce415 Mon Sep 17 00:00:00 2001 From: mskinner5278 Date: Fri, 17 May 2024 14:27:23 -0400 Subject: [PATCH 223/234] added exporter_enabled flag definition for non-nyx cases --- daq_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_utils.py b/daq_utils.py index ddf69058..9c61ea3e 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -73,6 +73,8 @@ def init_environment(): screenPixY = float(getBlConfig("screenPixY")) if beamline == 'nyx': exporter_enabled = bool(getBlConfig("exporterEnabled")) + else: + exporter_enabled = False try: unitScaling = float(getBlConfig("unitScaling")) From 90e0afe252a69ade084c2c7b4418331e19359e75 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 22 Jul 2024 15:13:49 -0400 Subject: [PATCH 224/234] Renamed and cleaned up unused layouts --- gui/control_main.py | 63 ++++++--------------------------------------- 1 file changed, 8 insertions(+), 55 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index cbcb7ffb..b7f49c24 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -424,7 +424,6 @@ def createSampleTab(self): colParamsGB = QtWidgets.QGroupBox() colParamsGB.setTitle("Acquisition") vBoxColParams1 = QtWidgets.QVBoxLayout() - hBoxColParams1 = QtWidgets.QHBoxLayout() colStartLabel = QtWidgets.QLabel("Oscillation Start:") colStartLabel.setFixedWidth(140) colStartLabel.setAlignment(QtCore.Qt.AlignCenter) @@ -444,7 +443,6 @@ def createSampleTab(self): ) if daq_utils.beamline == "fmx": self.osc_end_ledit.textChanged.connect(self.calcLifetimeCB) - hBoxColParams2 = QtWidgets.QHBoxLayout() colRangeLabel = QtWidgets.QLabel("Oscillation Width:") colRangeLabel.setFixedWidth(140) colRangeLabel.setAlignment(QtCore.Qt.AlignCenter) @@ -478,13 +476,6 @@ def createSampleTab(self): ) ) self.exp_time_ledit.textChanged.connect(self.checkEntryState) - #hBoxColParams2.addWidget(colRangeLabel) - #hBoxColParams2.addWidget(self.osc_range_ledit) - - #hBoxColParams2.addWidget(colExptimeLabel) - #hBoxColParams2.addWidget(self.exp_time_ledit) - hBoxColParams25 = QtWidgets.QHBoxLayout() - #hBoxColParams25.addWidget(self.stillModeCheckBox) totalExptimeLabel = QtWidgets.QLabel("Total Exposure Time (s):") totalExptimeLabel.setFixedWidth(155) totalExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) @@ -512,13 +503,6 @@ def createSampleTab(self): calcLifetimeButton.clicked.connect(self.calcLifetimeCB) self.sampleLifetimeReadback_ledit = QtWidgets.QLabel() self.calcLifetimeCB() - #hBoxColParams25.addWidget(totalExptimeLabel) - #hBoxColParams25.addWidget(self.totalExptime_ledit) - # if (daq_utils.beamline == "fmx"): - # hBoxColParams25.addWidget(calcLifetimeButton) - #hBoxColParams25.addWidget(sampleLifetimeLabel) - #hBoxColParams25.addWidget(self.sampleLifetimeReadback_ledit) - hBoxColParams22 = QtWidgets.QHBoxLayout() if daq_utils.beamline in ("fmx", "nyx"): if getBlConfig("attenType") == "RI": self.transmissionReadback = QtEpicsPVLabel( @@ -579,7 +563,6 @@ def createSampleTab(self): self.beamsizeComboBox.activated[str].connect(self.beamsizeComboActivatedCB) if daq_utils.beamline == "amx" or self.energy_pv.get() < 9000: self.beamsizeComboBox.setEnabled(False) - hBoxColParams3 = QtWidgets.QHBoxLayout() colEnergyLabel = QtWidgets.QLabel("Energy (eV):") colEnergyLabel.setAlignment(QtCore.Qt.AlignCenter) self.energyMotorEntry = QtEpicsPVLabel( @@ -613,7 +596,6 @@ def createSampleTab(self): hBoxColParams4.addWidget(self.beamWidth_ledit) hBoxColParams4.addWidget(colBeamHLabel) hBoxColParams4.addWidget(self.beamHeight_ledit) - hBoxColParams5 = QtWidgets.QHBoxLayout() colResoLabel = QtWidgets.QLabel("Edge Resolution:") colResoLabel.setAlignment(QtCore.Qt.AlignCenter) self.resolution_ledit = QtWidgets.QLineEdit() @@ -647,10 +629,6 @@ def createSampleTab(self): self.detDistMotorEntry.getEntry().returnPressed.connect(self.moveDetDistCB) self.moveDetDistButton = QtWidgets.QPushButton("Move Detector") self.moveDetDistButton.clicked.connect(self.moveDetDistCB) - #hBoxColParams3.addWidget(detDistLabel) - #hBoxColParams3.addWidget(self.detDistRBVLabel.getEntry()) - #hBoxColParams3.addWidget(detDistSPLabel) - #hBoxColParams3.addWidget(self.detDistMotorEntry.getEntry()) hBoxColParams6 = QtWidgets.QHBoxLayout() hBoxColParams6.setAlignment(QtCore.Qt.AlignLeft) hBoxColParams7 = QtWidgets.QHBoxLayout() @@ -720,8 +698,6 @@ def createSampleTab(self): hBoxColParams6.addWidget(self.protoComboBox) hBoxColParams7.addWidget(centeringLabel) hBoxColParams7.addWidget(self.centeringComboBox) - #hBoxColParams7.addWidget(colResoLabel) - #hBoxColParams7.addWidget(self.resolution_ledit) self.processingOptionsFrame = QFrame() self.hBoxProcessingLayout1 = QtWidgets.QHBoxLayout() self.hBoxProcessingLayout1.setAlignment(QtCore.Qt.AlignLeft) @@ -895,29 +871,11 @@ def createSampleTab(self): vector_widgets_layout.addLayout(hBoxVectorLayout2) self.vectorParamsFrame.setLayout(vector_widgets_layout) - vBoxColParams1.addLayout(hBoxColParams1) - vBoxColParams1.addLayout(hBoxColParams2) - vBoxColParams1.addLayout(hBoxColParams25) - vBoxColParams1.addLayout(hBoxColParams22) - vBoxColParams1.addLayout(hBoxColParams3) - #vBoxColParams1.addLayout(hBoxColParams7) - #vBoxColParams1.addLayout(hBoxColParams6) - #vBoxColParams1.addWidget(self.rasterParamsFrame) - #vBoxColParams1.addWidget(self.multiColParamsFrame) - #vBoxColParams1.addWidget(self.vectorParamsFrame) - #vBoxColParams1.addWidget(self.characterizeParamsFrame) - #vBoxColParams1.addWidget(self.processingOptionsFrame) - mikesGB = QtWidgets.QGroupBox() - mikesGB.setTitle("Acquisition") + + paramsGridGB = QtWidgets.QGroupBox() + paramsGridGB.setTitle("Acquisition") - paramSubspace = QtWidgets.QGridLayout() - - #paramSubspace.setColumnMinimumWidth(0, 140) - #paramSubspace.setColumnMinimumWidth(1, 90) - #paramSubspace.setColumnMinimumWidth(2, 140) - #paramSubspace.setColumnMinimumWidth(3, 90) - #paramSubspace.setColumnMinimumWidth(4, 90) - #paramSubspace.setColumnStretch(1) learn about stretch factor and then update + paramSubspace = QtWidgets.QGridLayout() # Parameter Collection Column 1, Labels @@ -940,19 +898,15 @@ def createSampleTab(self): # Parameter Collection Column 3, Labels paramSubspace.addWidget(detDistLabel,0,2, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(colResoLabel,1,2, alignment=QtCore.Qt.AlignLeft) - #paramSubspace.addWidget(detDistSPLabel,1,2, alignment=QtCore.Qt.AlignLeft) - #hBoxColParams7.addWidget(colResoLabel) paramSubspace.addWidget(colEnergyLabel,2,2, alignment=QtCore.Qt.AlignLeft) - #paramSubspace.addWidget(energySPLabel,3,2, alignment=QtCore.Qt.AlignLeft) colTransmissionLabel.setAlignment(QtCore.Qt.AlignLeft) paramSubspace.addWidget(colTransmissionLabel,3,2, alignment=QtCore.Qt.AlignLeft) transmisionSPLabel.setAlignment(QtCore.Qt.AlignLeft) - #paramSubspace.addWidget(transmisionSPLabel,3,2, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(beamsizeLabel,4,2, alignment=QtCore.Qt.AlignLeft) + # Parameter Collection Column 4, Input Boxes paramSubspace.addWidget(self.detDistMotorEntry.getEntry(),0,3, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(self.resolution_ledit,1,3, alignment=QtCore.Qt.AlignLeft) - #hBoxColParams7.addWidget(self.resolution_ledit) if daq_utils.beamline == "fmx": if getBlConfig(SET_ENERGY_CHECK): paramSubspace.addWidget(moveEnergyButton,2,3, alignment=QtCore.Qt.AlignLeft) @@ -960,9 +914,9 @@ def createSampleTab(self): paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) else: paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.transmission_ledit,3,3, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(self.beamsizeComboBox,4,3, alignment=QtCore.Qt.AlignLeft) + # Param Collection Column 5, RBV paramSubspace.addWidget(self.energyReadback,2,4, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(self.detDistRBVLabel.getEntry(),0,4, alignment=QtCore.Qt.AlignLeft) @@ -978,7 +932,7 @@ def createSampleTab(self): improvedParamSpacing.addWidget(self.vectorParamsFrame) improvedParamSpacing.addWidget(self.characterizeParamsFrame) improvedParamSpacing.addWidget(self.processingOptionsFrame) - mikesGB.setLayout(improvedParamSpacing) + paramsGridGB.setLayout(improvedParamSpacing) self.rasterParamsFrame.hide() self.multiColParamsFrame.hide() @@ -989,8 +943,7 @@ def createSampleTab(self): self.albulaDispCheckBox = QCheckBox("Display Data (Albula)") self.albulaDispCheckBox.setChecked(False) hBoxDisplayOptionLayout.addWidget(self.albulaDispCheckBox) - #vBoxMainColLayout.addWidget(colParamsGB) - vBoxMainColLayout.addWidget(mikesGB) + vBoxMainColLayout.addWidget(paramsGridGB) vBoxMainColLayout.addWidget(self.dataPathGB) self.mainColFrame.setLayout(vBoxMainColLayout) From 2ddaf14c2833387c525ee259070fd00083ce2ddd Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Sat, 14 Sep 2024 18:13:23 -0400 Subject: [PATCH 225/234] Fixed sample camera for FMX/AMX --- config_params.py | 2 +- gui/control_main.py | 47 ++++++++++++++++++++++++++++----------------- threads.py | 42 ++++++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/config_params.py b/config_params.py index d5ce421e..8f1d4595 100644 --- a/config_params.py +++ b/config_params.py @@ -64,7 +64,7 @@ class OnMountAvailOptions(Enum): AUTO_RASTER = 2 # Mounts, centers and takes 2 orthogonal rasters HUTCH_TIMER_DELAY = 500 -SAMPLE_TIMER_DELAY = 100 +SAMPLE_TIMER_DELAY = 40 SERVER_CHECK_DELAY = 2000 FAST_DP_MIN_NODES = 4 diff --git a/gui/control_main.py b/gui/control_main.py index b7f49c24..8525227b 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -19,7 +19,7 @@ from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel from qtpy import QtCore, QtGui, QtWidgets -from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer +from qtpy.QtCore import QModelIndex, QRectF, Qt, QTimer, QMutex, QMutexLocker from qtpy.QtGui import QIntValidator from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication @@ -252,6 +252,7 @@ def __init__(self): self.detDistRBVLabel.getEntry().text() ) # this is to fix the current val being overwritten by reso self.proposalID = -999999 + self.sampleCameraMutex = QMutex() if len(sys.argv) > 1: if sys.argv[1] == "master": self.changeControlMasterCB(1) @@ -302,19 +303,20 @@ def parseXRFTable(self): def closeEvent(self, evnt): + self.sampleCameraThread.stop() evnt.accept() sys.exit() # doing this to close any windows left open def initVideo2(self, frequency): - #self.captureHighMag = cv2.VideoCapture(daq_utils.highMagCamURL) + self.captureHighMag = daq_utils.highMagCamURL logger.debug('highMagCamURL: "' + daq_utils.highMagCamURL + '"') def initVideo4(self, frequency): - #self.captureHighMagZoom = cv2.VideoCapture(daq_utils.highMagZoomCamURL) + self.captureHighMagZoom = daq_utils.highMagZoomCamURL logger.debug('highMagZoomCamURL: "' + daq_utils.highMagZoomCamURL + '"') def initVideo3(self, frequency): - #self.captureLowMagZoom = cv2.VideoCapture(daq_utils.lowMagZoomCamURL) + self.captureLowMagZoom = daq_utils.lowMagZoomCamURL logger.debug('lowMagZoomCamURL: "' + daq_utils.lowMagZoomCamURL + '"') def createSampleTab(self): @@ -402,8 +404,8 @@ def createSampleTab(self): vBoxTreeButtsLayoutLeft.addWidget(self.popUserScreen) vBoxTreeButtsLayoutLeft.addWidget(warmupButton) vBoxTreeButtsLayoutRight.addWidget(self.closeShutterButton) - vBoxTreeButtsLayoutRight.addWidget(self.parkRobotButton) vBoxTreeButtsLayoutRight.addWidget(unmountSampleButton) + vBoxTreeButtsLayoutRight.addWidget(self.parkRobotButton) vBoxTreeButtsLayoutRight.addWidget(deQueueSelectedButton) vBoxTreeButtsLayoutRight.addWidget(emptyQueueButton) vBoxTreeButtsLayoutRight.addWidget(endVisitButton) @@ -1003,6 +1005,7 @@ def createSampleTab(self): ) # this sets up lowMagDigiZoom if self.zoom1FrameRatePV.get() != 0: #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) + self.captureLowMag = daq_utils.lowMagCamURL logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) @@ -1525,12 +1528,16 @@ def createSampleTab(self): self.captureLowMag = daq_utils.lowMagCamURL self.capture = self.captureLowMag - #self.sampleCameraThread = VideoThread( - # parent=self, delay=SAMPLE_TIMER_DELAY, camera_object=self.capture - #) - self.sampleCameraThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=daq_utils.highMagCamURL - ) + if daq_utils.beamline == "nyx": + self.sampleCameraThread = VideoThread( + parent=self, delay=HUTCH_TIMER_DELAY, url=daq_utils.highMagCamURL + ) + else: + self.sampleCameraThread = VideoThread( + parent=self, delay=SAMPLE_TIMER_DELAY, mjpg_url=self.capture + ) + self.sampleZoomChangeSignal.connect(self.sampleCameraThread.updateCam) + self.sampleCameraThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item, frame) ) @@ -1558,7 +1565,11 @@ def createSampleTab(self): serverCheckThread.start() def updateCam(self, pixmapItem: "QGraphicsPixmapItem", frame): - pixmapItem.setPixmap(frame) + if pixmapItem == self.pixmap_item: + with QMutexLocker(self.sampleCameraMutex): + pixmapItem.setPixmap(frame) + else: + pixmapItem.setPixmap(frame) def annealButtonCB(self): try: @@ -1709,7 +1720,6 @@ def zoomLevelToggledCB(self, identifier): zoomedCursorX = self.getBeamCenterX() - self.centerMarkerCharOffsetX zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): - self.flushBuffer(self.captureLowMagZoom) self.capture = self.captureLowMagZoom fov["x"] = daq_utils.lowMagFOVx / 2.0 fov["y"] = daq_utils.lowMagFOVy / 2.0 @@ -1743,7 +1753,6 @@ def zoomLevelToggledCB(self, identifier): self.beamSizeYPixels, ) elif self.zoom1Radio.isChecked(): - self.flushBuffer(self.captureLowMag) self.capture = self.captureLowMag fov["x"] = daq_utils.lowMagFOVx fov["y"] = daq_utils.lowMagFOVy @@ -1764,7 +1773,6 @@ def zoomLevelToggledCB(self, identifier): self.beamSizeYPixels, ) elif self.zoom4Radio.isChecked(): - self.flushBuffer(self.captureHighMagZoom) self.capture = self.captureHighMagZoom fov["x"] = daq_utils.highMagFOVx / 2.0 fov["y"] = daq_utils.highMagFOVy / 2.0 @@ -1802,7 +1810,6 @@ def zoomLevelToggledCB(self, identifier): self.beamSizeYPixels, ) elif self.zoom3Radio.isChecked(): - self.flushBuffer(self.captureHighMag) self.capture = self.captureHighMag fov["x"] = daq_utils.highMagFOVx fov["y"] = daq_utils.highMagFOVy @@ -1828,7 +1835,8 @@ def zoomLevelToggledCB(self, identifier): def saveVidSnapshotButtonCB(self): comment, useOlog, ok = SnapCommentDialog.getComment() if ok: - self.saveVidSnapshotCB(comment, useOlog) + with QMutexLocker(self.sampleCameraMutex): + self.saveVidSnapshotCB(comment, useOlog) def saveVidSnapshotCB( self, comment="", useOlog=False, reqID=None, rasterHeatJpeg=None @@ -4525,7 +4533,10 @@ def popUserScreenCB(self): self.popupServerMessage("You don't have control") def parkRobotCB(self): - self.send_to_server("parkRobot()") + if daq_utils.beamline == "nyx": + self.send_to_server("parkRobot()") + else: + self.send_to_server("parkGripper") def closePhotonShutterCB(self): self.photonShutterClose_pv.put(1) diff --git a/threads.py b/threads.py index 389ca057..a8a5457e 100644 --- a/threads.py +++ b/threads.py @@ -38,50 +38,49 @@ def camera_refresh(self): self.frame_ready.emit(pixmap_orig) self.showing_error = True - if self.camera_object: - - # Initialize a variable to store the most recent frame - most_recent_frame = None - - timeout = self.delay / 1000.0 - start_time = time.time() - if self.camera_object.grab(): - retval, frame = self.camera_object.retrieve() - if retval: - most_recent_frame = frame - self.currentFrame = most_recent_frame + if self.video_capture: + if self.new_mjpg_url != self.old_mjpg_url: + self.video_capture.open(self.new_mjpg_url) + self.old_mjpg_url = self.new_mjpg_url + retval,self.currentFrame = self.video_capture.read() if self.currentFrame is None: #logger.debug('no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct') return - + height,width=self.currentFrame.shape[:2] qimage= QtGui.QImage(self.currentFrame,width,height,3*width,QtGui.QImage.Format_RGB888) qimage = qimage.rgbSwapped() pixmap_orig = QtGui.QPixmap.fromImage(qimage) if self.width and self.height: pixmap_orig = pixmap_orig.scaled(self.width, self.height) + if not self.showing_error: self.frame_ready.emit(pixmap_orig) - def __init__(self, *args, delay=1000, url='', camera_object=None, width=None, height=None,**kwargs): + def __init__(self, *args, delay=1000, url='', mjpg_url=None, width=None, height=None,**kwargs): self.delay = delay self.width = width self.height = height self.url = url - self.camera_object = camera_object - if self.camera_object: - self.camera_object = cv2.VideoCapture(camera_object) #camera_object - self.camera_object.set(cv2.CAP_PROP_BUFFERSIZE, 1) - + self.mjpg_url = mjpg_url + self.old_mjpg_url = None + self.new_mjpg_url = None + self.video_capture = None + if self.mjpg_url and self.mjpg_url.lower().endswith(".mjpg"): + self.video_capture = cv2.VideoCapture(self.mjpg_url) + self.old_mjpg_url = self.mjpg_url + self.new_mjpg_url = self.mjpg_url + self.mjpg_url = None self.showing_error = False self.is_running = True QThread.__init__(self, *args, **kwargs) - def updateCam(self, camera_object): - self.camera_object = camera_object + def updateCam(self, url): + if url.lower().endswith(".mjpg"): + self.new_mjpg_url = url def run(self): while self.is_running: @@ -91,6 +90,7 @@ def run(self): def stop(self): self.is_running = False + self.wait() class RaddoseThread(QThread): From 2b711580270a56eb36624055222537d51e791ef2 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Mon, 16 Sep 2024 10:04:21 -0400 Subject: [PATCH 226/234] Readded calc lifetime for AMX/FMX --- gui/control_main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gui/control_main.py b/gui/control_main.py index 8525227b..0cc55166 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -500,7 +500,7 @@ def createSampleTab(self): daq_utils.pvLookupDict["sampleLifetime"], self, 70, 2 ) self.sampleLifetimeReadback_ledit = self.sampleLifetimeReadback.getEntry() - else: + elif daq_utils.beamline == "fmx": calcLifetimeButton = QtWidgets.QPushButton("Calc. Lifetime") calcLifetimeButton.clicked.connect(self.calcLifetimeCB) self.sampleLifetimeReadback_ledit = QtWidgets.QLabel() @@ -891,6 +891,8 @@ def createSampleTab(self): paramSubspace.addWidget(colExptimeLabel,3,0, alignment=QtCore.Qt.AlignLeft) totalExptimeLabel.setAlignment(QtCore.Qt.AlignLeft) paramSubspace.addWidget(totalExptimeLabel,4,0, alignment=QtCore.Qt.AlignLeft) + if daq_utils.beamline in ['amx', 'fmx']: + paramSubspace.addWidget(sampleLifetimeLabel, 5, 0, alignment=QtCore.Qt.AlignLeft) # Parameter Collection Column 2, Input Boxes paramSubspace.addWidget(self.osc_start_ledit,1,1, alignment=QtCore.Qt.AlignLeft) paramSubspace.addWidget(self.osc_end_ledit,2,1, alignment=QtCore.Qt.AlignLeft) @@ -905,6 +907,8 @@ def createSampleTab(self): paramSubspace.addWidget(colTransmissionLabel,3,2, alignment=QtCore.Qt.AlignLeft) transmisionSPLabel.setAlignment(QtCore.Qt.AlignLeft) paramSubspace.addWidget(beamsizeLabel,4,2, alignment=QtCore.Qt.AlignLeft) + if daq_utils.beamline in ['amx', 'fmx']: + paramSubspace.addWidget(self.sampleLifetimeReadback_ledit, 5, 1, alignment=QtCore.Qt.AlignLeft) # Parameter Collection Column 4, Input Boxes paramSubspace.addWidget(self.detDistMotorEntry.getEntry(),0,3, alignment=QtCore.Qt.AlignLeft) From 075ae2b43d983e5dbb3d4232c40969b761b976d6 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 17 Sep 2024 12:16:49 -0400 Subject: [PATCH 227/234] minor import fixes for NYX compatibility --- daq_main_common.py | 161 ++++++++++++++++++++++++++++++--------------- start_bs.py | 8 ++- 2 files changed, 113 insertions(+), 56 deletions(-) diff --git a/daq_main_common.py b/daq_main_common.py index d959bcad..ecdd560c 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -34,60 +34,113 @@ def setGovState(state): setGovRobot(gov_robot, state) - -functions = [anneal, - set_beamsize, - importSpreadsheet, - mvaDescriptor, - setTrans, - loop_center_xrec, - autoRasterLoop, - snakeRaster, - ispybLib.insertRasterResult, - backlightBrighter, - backlightDimmer, - changeImageCenterHighMag, - changeImageCenterLowMag, - center_on_click, - runDCQueue, - warmupGripper, - dryGripper, - enableDewarTscreen, - parkGripper, - stopDCQueue, - continue_data_collection, - mountSample, - unmountSample, - reprocessRaster, - fastDPNodes, - spotNodes, - unmountCold, - openPort, - set_beamcenter, - closePorts, - clearMountedSample, - recoverRobot, - rebootEMBL, - restartEMBL, - openGripper, - closeGripper, - homePins, - setSlit1X, - setSlit1Y, - testRobot, - setGovState, - move_omega, - lockGUI, - unlockGUI, - DewarAutoFillOff, - DewarAutoFillOn, - logMe, - unlatchGov, - backoffDetector, - enableMount, - robotOn, - set_energy - ] +if daq_utils.beamline != "nyx": + functions = [anneal, + set_beamsize, + importSpreadsheet, + mvaDescriptor, + setTrans, + loop_center_xrec, + autoRasterLoop, + snakeRaster, + ispybLib.insertRasterResult, + backlightBrighter, + backlightDimmer, + changeImageCenterHighMag, + changeImageCenterLowMag, + center_on_click, + runDCQueue, + warmupGripper, + dryGripper, + enableDewarTscreen, + parkGripper, + stopDCQueue, + continue_data_collection, + mountSample, + unmountSample, + reprocessRaster, + fastDPNodes, + spotNodes, + unmountCold, + openPort, + set_beamcenter, + closePorts, + clearMountedSample, + recoverRobot, + rebootEMBL, + restartEMBL, + openGripper, + closeGripper, + homePins, + setSlit1X, + setSlit1Y, + testRobot, + setGovState, + move_omega, + lockGUI, + unlockGUI, + DewarAutoFillOff, + DewarAutoFillOn, + logMe, + unlatchGov, + backoffDetector, + enableMount, + robotOn, + set_energy + ] +else: + functions = [ + set_beamsize, + importSpreadsheet, + mvaDescriptor, + setTrans, + loop_center_xrec, + autoRasterLoop, + snakeRaster, + backlightBrighter, + backlightDimmer, + changeImageCenterHighMag, + changeImageCenterLowMag, + center_on_click, + runDCQueue, + warmupGripper, + dryGripper, + enableDewarTscreen, + parkGripper, + stopDCQueue, + continue_data_collection, + mountSample, + unmountSample, + reprocessRaster, + fastDPNodes, + spotNodes, + unmountCold, + openPort, + set_beamcenter, + closePorts, + clearMountedSample, + recoverRobot, + rebootEMBL, + restartEMBL, + openGripper, + closeGripper, + homePins, + setSlit1X, + setSlit1Y, + testRobot, + setGovState, + move_omega, + lockGUI, + unlockGUI, + DewarAutoFillOff, + DewarAutoFillOn, + logMe, + unlatchGov, + backoffDetector, + enableMount, + robotOn, + set_energy + ] whitelisted_functions: "Dict[str, Callable]" = { func.__name__: func for func in functions diff --git a/start_bs.py b/start_bs.py index 945dd50f..1f606a31 100755 --- a/start_bs.py +++ b/start_bs.py @@ -9,8 +9,6 @@ from mxtools.governor import _make_governors from ophyd.signal import EpicsSignalBase EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style -from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, - TopAlignerFast, TopAlignerSlow, GoniometerStack) #12/19 - author unknown. DAMA can help """ @@ -114,6 +112,8 @@ class SampleXYZ(Device): omega = Cpt(EpicsMotor, ':O}Mtr') if (beamline=="amx"): + from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, + TopAlignerFast, TopAlignerSlow, GoniometerStack) mercury = ABBIXMercury('XF:17IDB-ES:AMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] @@ -146,6 +146,8 @@ class SampleXYZ(Device): elif beamline == "fmx": + from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, + TopAlignerFast, TopAlignerSlow, GoniometerStack) mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] @@ -180,11 +182,13 @@ class SampleXYZ(Device): elif beamline=="nyx": from mxbluesky.devices.md2 import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice + two_click_low = mount_pos = loop_detector = top_aligner_fast = top_aligner_slow = work_pos = None mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] vdcm = VerticalDCM('XF:17IDA-OP:FMX{Mono:DCM', name='vdcm') md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") + gonio = md2 shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') front_light = LightDevice('XF:19IDC-ES{MD2}:Front', name='front_light') From 7a57ce792c1f60c7b9a0e2a9e852725a23d4034d Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 17 Sep 2024 12:33:34 -0400 Subject: [PATCH 228/234] [from shekar] function definition fix --- gui/vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/vector.py b/gui/vector.py index 391ddfc8..ef14a501 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -342,7 +342,7 @@ def clear_vector(self, scene: QtWidgets.QGraphicsScene) -> None: def transform_vector_coords( self, prev_coords: "dict[str, float]", current_raw_coords: "dict[str, float]" - ) -> dict[str, float]: + ) -> "dict[str, float]": """Updates y and z co-ordinates of vector points when they are moved This function tweaks the y and z co-ordinates such that when a vector start or From 896024058c8023f34f0275b1ad75a4439749ccea Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 17 Sep 2024 12:34:04 -0400 Subject: [PATCH 229/234] NYX does not have the eiger on the network --- gui/control_main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 0cc55166..66b7596e 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -175,8 +175,9 @@ def __init__(self): self.redPen = QtGui.QPen(QtCore.Qt.red) self.bluePen = QtGui.QPen(QtCore.Qt.blue) self.yellowPen = QtGui.QPen(QtCore.Qt.yellow) - self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], - gov_message_pv_name=daq_utils.pvLookupDict["governorMessage"],) + if daq_utils.beamline != "nyx": + self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], + gov_message_pv_name=daq_utils.pvLookupDict["governorMessage"],) self.initUI() self.initOphyd() self.govStateMessagePV = PV(daq_utils.pvLookupDict["governorMessage"]) @@ -4737,7 +4738,8 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): firstFilename = daq_utils.create_filename(prefix_long, fnumstart) if validate_hdf5: if validation.validate_master_HDF5_file(firstFilename): - self.albulaInterface.open_file(firstFilename) + if daq_utils.beamline != "nyx": + self.albulaInterface.open_file(firstFilename) else: QtWidgets.QMessageBox.information( self, From 99c896bd831b4fc967b68c9220c50b7c34846d03 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 17 Sep 2024 12:56:27 -0400 Subject: [PATCH 230/234] added NYX configuration back and removed node list carveout --- config_params.py | 2 +- gui/dialog/staff_screen.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/config_params.py b/config_params.py index 8f1d4595..2b40889f 100644 --- a/config_params.py +++ b/config_params.py @@ -91,7 +91,7 @@ class OnMountAvailOptions(Enum): DEWAR_SECTORS = {'amx':8, 'fmx':8, 'nyx':8} PUCKS_PER_DEWAR_SECTOR = {'amx':3, 'fmx':3, 'nyx':3} -cryostreamTempPV = {"amx": "XF:17IDB-ES:AMX{CS:1}SAMPLE_TEMP_RBV", "fmx": "FMX:cs700:gasT-I"} +cryostreamTempPV = {"amx": "XF:17IDB-ES:AMX{CS:1}SAMPLE_TEMP_RBV", "fmx": "FMX:cs700:gasT-I", "nyx":"XF:19ID2:CS700:TEMP"} VALID_EXP_TIMES = { "amx": {"min": 0.005, "max": 1, "digits": 3}, diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index afbb21c7..1375f7be 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -234,10 +234,7 @@ def show(self): def getSpotNodeList(self): nodeList = [] for i in range(0, self.spotNodeCount): - if daq_utils.beamline == 'nyx': - nodeList.append(int(getBlConfig("spotNode"+str(i+1)).split('epu')[1])) - else: - nodeList.append(int(getBlConfig("spotNode"+str(i+1)).split('cpu')[1])) + nodeList.append(int(getBlConfig("spotNode"+str(i+1)).split('cpu')[1])) return nodeList def getFastDPNodeList(self): From 04e3617125f2415205ccdb3b4951f92b964de475 Mon Sep 17 00:00:00 2001 From: Michael Skinner Date: Tue, 17 Sep 2024 13:17:58 -0400 Subject: [PATCH 231/234] config param update for nyx --- config_params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_params.py b/config_params.py index 2b40889f..5453b6da 100644 --- a/config_params.py +++ b/config_params.py @@ -134,4 +134,4 @@ class OnMountAvailOptions(Enum): "S": ["V0", "H0"], "L": ["V1", "H1"] } -OPHYD_COLLECTIONS = {"amx": False, "fmx": False, "nyx": False} +OPHYD_COLLECTIONS = {"amx": False, "fmx": False, "nyx": True} From 7526591deca7404c82d484c330f71424b48d6737 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Fri, 20 Sep 2024 09:18:06 -0400 Subject: [PATCH 232/234] Moved md2_flyers to mxbluesky/devices --- mxbluesky/devices/__init__.py | 2 +- md2_flyers.py => mxbluesky/devices/md2_flyers.py | 0 start_bs.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename md2_flyers.py => mxbluesky/devices/md2_flyers.py (100%) diff --git a/mxbluesky/devices/__init__.py b/mxbluesky/devices/__init__.py index 191b3f96..70cf77c8 100644 --- a/mxbluesky/devices/__init__.py +++ b/mxbluesky/devices/__init__.py @@ -4,7 +4,7 @@ from .auto_center import * from .top_align import * from .zebra import * -except: +except Exception as e: pass diff --git a/md2_flyers.py b/mxbluesky/devices/md2_flyers.py similarity index 100% rename from md2_flyers.py rename to mxbluesky/devices/md2_flyers.py diff --git a/start_bs.py b/start_bs.py index 1f606a31..826a721c 100755 --- a/start_bs.py +++ b/start_bs.py @@ -202,7 +202,7 @@ class SampleXYZ(Device): from mxtools.eiger import EigerSingleTriggerV26 detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) #from nyxtools.flyer_eiger2 import NYXEiger2Flyer - from md2_flyers import MD2StandardFlyer, MD2VectorFlyer, MD2RasterFlyer + from mxbluesky.md2_flyers import MD2StandardFlyer, MD2VectorFlyer, MD2RasterFlyer flyer = MD2StandardFlyer(md2, detector) vector_flyer = MD2VectorFlyer(md2, detector) raster_flyer = MD2RasterFlyer(md2, detector) From 7a605e10a2a7195b26a2134210369fcbc4f5eb68 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Fri, 20 Sep 2024 11:23:25 -0400 Subject: [PATCH 233/234] Fixed beamavailable widget for different beamlines --- gui/control_main.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index 66b7596e..481e8853 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -2217,12 +2217,20 @@ def processRingCurrent(self, ringCurrentVal): function is processThreClickCentering ''' def processThreeClickCentering(self, beamAvailVal): - if beamAvailVal == '0': - self.beamAvailLabel.setText("Beam Available") - self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") + if daq_utils.beamline == 'nyx': + if beamAvailVal == '0': + self.beamAvailLabel.setText("Beam Available") + self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") + else: + self.beamAvailLabel.setText(beamAvailVal) + self.beamAvailLabel.setStyleSheet("background-color: yellow") else: - self.beamAvailLabel.setText(beamAvailVal) - self.beamAvailLabel.setStyleSheet("background-color: yellow") + if beamAvailVal == "1": + self.beamAvailLabel.setText("Beam Available") + self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") + elif beamAvailVal == "0": + self.beamAvailLabel.setText("No Beam") + self.beamAvailLabel.setStyleSheet("background-color: red") def processSampleExposed(self, sampleExposedVal): if int(sampleExposedVal) == 1: From cb86a043318d221996e7b572b4287da392da5317 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Fri, 20 Sep 2024 11:24:01 -0400 Subject: [PATCH 234/234] Reduced repitition in daq_main_common --- daq_main_common.py | 60 ++++------------------------------------------ 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/daq_main_common.py b/daq_main_common.py index ecdd560c..dd459370 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -34,62 +34,7 @@ def setGovState(state): setGovRobot(gov_robot, state) -if daq_utils.beamline != "nyx": - functions = [anneal, - set_beamsize, - importSpreadsheet, - mvaDescriptor, - setTrans, - loop_center_xrec, - autoRasterLoop, - snakeRaster, - ispybLib.insertRasterResult, - backlightBrighter, - backlightDimmer, - changeImageCenterHighMag, - changeImageCenterLowMag, - center_on_click, - runDCQueue, - warmupGripper, - dryGripper, - enableDewarTscreen, - parkGripper, - stopDCQueue, - continue_data_collection, - mountSample, - unmountSample, - reprocessRaster, - fastDPNodes, - spotNodes, - unmountCold, - openPort, - set_beamcenter, - closePorts, - clearMountedSample, - recoverRobot, - rebootEMBL, - restartEMBL, - openGripper, - closeGripper, - homePins, - setSlit1X, - setSlit1Y, - testRobot, - setGovState, - move_omega, - lockGUI, - unlockGUI, - DewarAutoFillOff, - DewarAutoFillOn, - logMe, - unlatchGov, - backoffDetector, - enableMount, - robotOn, - set_energy - ] -else: - functions = [ +functions = [ set_beamsize, importSpreadsheet, mvaDescriptor, @@ -142,6 +87,9 @@ def setGovState(state): set_energy ] +if daq_utils.beamline != "nyx": + functions = functions + [anneal] + whitelisted_functions: "Dict[str, Callable]" = { func.__name__: func for func in functions }