diff --git a/ModuleFixTarget.py b/ModuleFixTarget.py
new file mode 100644
index 0000000..487de8e
--- /dev/null
+++ b/ModuleFixTarget.py
@@ -0,0 +1,784 @@
+#!/usr/bin/env python
+# *-----------------------------------------------------------------------*
+# | |
+# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
+# | |
+# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
+# *-----------------------------------------------------------------------*
+'''
+Module to handle FixTarget acquisition
+
+This contains a Widget to handle FixTargetFrames and fiducials, calculate final target positions etc.
+'''
+
+#https://stackoverflow.com/questions/27909658/json-encoder-and-decoder-for-complex-numpy-arrays
+
+# import yaml
+# class Dice(tuple):
+# def __new__(cls, a, b):
+# return tuple.__new__(cls, [a, b])
+# def __repr__(self):
+# return "Dice(%s,%s)" % self
+# d=Dice(3,6)
+# print(d)
+# print(yaml.dump(d))
+# def dice_representer(dumper, data):
+# return dumper.represent_scalar(u'!dice', u'%sd%s' % data)
+# yaml.add_representer(Dice, dice_representer)
+# def dice_constructor(loader, node):
+# value = loader.construct_scalar(node)
+# a, b = map(int, value.split('d'))
+# return Dice(a, b)
+# yaml.add_constructor(u'!dice', dice_constructor)
+# print(yaml.load("initial hit points: !dice 8d4"))
+
+import logging
+_log=logging.getLogger(__name__)
+
+import os, pickle, json, yaml,base64
+import numpy as np
+import pyqtgraph as pg
+from PyQt5.QtCore import Qt, QFileInfo, pyqtSignal, pyqtSlot
+from PyQt5.QtWidgets import (
+ QTableWidgetItem,
+ QFileDialog,
+ QGroupBox,
+ QWidget,
+ QGridLayout,
+ QTableWidget,
+ QAbstractItemView,
+ QItemDelegate,
+ QVBoxLayout,
+ QLabel,
+ QPushButton,
+ QApplication,
+ QMainWindow,
+ QHeaderView,
+ QLineEdit,
+ QSpinBox,
+ QMessageBox,
+ QAction,
+ QComboBox,
+ QCheckBox)
+
+
+class MyJsonEncoder(json.JSONEncoder):
+ """ Special json encoder for numpy types """
+ def default(self, obj):
+ if isinstance(obj, np.integer):
+ return int(obj)
+ elif isinstance(obj, np.floating):
+ return float(obj)
+ elif isinstance(obj, np.ndarray):
+ data_b64=base64.b64encode(obj.data)
+ return dict(__ndarray__=data_b64,dtype=str(obj.dtype),shape=obj.shape)
+ #return obj.tolist()
+ elif isinstance(obj, set):
+ return list(obj)
+ if type(obj) not in (dict,list,str,int):
+ _log.error('dont know how to json')
+ return repr(obj)
+ return json.JSONEncoder.default(self, obj)
+
+def MyJsonDecoder(dct):
+ if isinstance(dct, dict) and '__ndarray__' in dct:
+ data = base64.b64decode(dct['__ndarray__'])
+ return np.frombuffer(data[:-1], dct['dtype']).reshape(dct['shape'])
+ return dct
+
+def yaml_repr_numpy(dumper, data):
+ return dumper.represent_scalar(u'!np_array', u'%s' % repr(data))
+yaml.add_representer(np.ndarray, yaml_repr_numpy)
+
+def yaml_cnstr_numpy(loader, node):
+ value = loader.construct_scalar(node)
+ a=eval(value[6:-1])
+ return np.array(a)
+yaml.add_constructor(u'!np_array', yaml_cnstr_numpy)
+
+
+
+
+class MarkerDelegate(QItemDelegate):
+ def createEditor(self, parent, option, index):
+ comboBox=QLineEdit(parent)
+ comboBox.editingFinished.connect(self.emitCommitData)
+ return comboBox
+
+ def setEditorData(self, editor, index):
+ # pos = comboBox.findText(index.model().data(index), Qt.MatchExactly)
+ # comboBox.setCurrentIndex(pos)
+ editor.setText("{}".format(index.model().data(index)))
+
+ def setModelData(self, editor, model, index):
+ print("model {}".format(model))
+ print(
+ "delegate setData: {}x{} => {}".format(
+ index.row(), index.column(), editor.text()
+ )
+ )
+ model.setData(index, editor.text())
+ model.setData(index, float(editor.text()), Qt.UserRole)
+
+ def emitCommitData(self):
+ print("imagedelegate emitting")
+ self.commitData.emit(self.sender())
+
+
+class WndFixTarget(QWidget):
+ prefixSelected = pyqtSignal(str)
+ dataFileLoaded = pyqtSignal(str)
+ prelocatedDataUpdated = pyqtSignal()
+ markersDeleted = pyqtSignal()
+ markerAdded = pyqtSignal(bool, list) # emits is_fiducial(boolean), [x, y, cx, cy]
+ selectedRowChanged = pyqtSignal(int)
+ moveFastStageRequest = pyqtSignal(float, float)
+
+ def __init__(self, parent=None,data=None):
+ super(WndFixTarget, self).__init__(parent)
+ self._xtals_transformed = True
+
+ self._current_row = None
+
+ layout = QVBoxLayout()
+
+ self.setLayout(layout)
+ frame = QWidget()
+ bl = QGridLayout()
+ frame.setLayout(bl)
+
+ btnLd = QPushButton("Load Datafile")
+ btnLd.clicked.connect(lambda: self.load_file(None))
+ bl.addWidget(btnLd, 0, 0)
+
+ btnSv = QPushButton("Save Datafile")
+ btnSv.clicked.connect(lambda: self.save_file())
+ bl.addWidget(btnSv, 0, 1)
+
+ # but = QPushButton("Random Data")
+ # but.clicked.connect(self.generate_random_data)
+ # bl.addWidget(but, 4, 0)
+ # self._num_random_points = QSpinBox()
+ # self._num_random_points.setMinimum(2)
+ # self._num_random_points.setMaximum(10000)
+ # self._num_random_points.setValue(10)
+ # self._num_random_points.setSuffix(" points")
+ # bl.addWidget(self._num_random_points, 4, 1)
+
+ btnDump = QPushButton("dump")
+ btnDump.clicked.connect(self.dump_data)
+ bl.addWidget(btnDump, 0, 2)
+
+ self._cbType=cb=QComboBox()
+ #cb.addItem("C")
+ #cb.addItem("C++")
+ cb.addItems(["Fiducial", "FixTarget(12.5x12.5)", "FixTarget(23.0x23.0)", "FixTarget()", "Grid()"])
+
+
+ #cb.currentIndexChanged.connect(self.selectionchange)
+
+ bl.addWidget(cb, 1,0,1,1)
+ self._txtParam=param=QLineEdit()
+ bl.addWidget(param, 1, 1,1,1)
+
+ self._btnAdd=btnAdd = QPushButton("add obj")
+ btnAdd.clicked.connect(self.add_obj)
+ bl.addWidget(btnAdd, 1, 2,1,1)
+
+
+ # but = QPushButton("Dump to console")
+ # but.clicked.connect(self.dump_data)
+ # bl.addWidget(but, 10, 0, 1, 1)
+
+ but = QCheckBox("collect fiducials")
+ but.setChecked(False)
+ but.setToolTip("Collect or not the fiducial positions.")
+ self._collect_fiducials = False
+ but.stateChanged.connect(self.set_collect_fiducials)
+ bl.addWidget(but, 10, 0, 1, 1)
+
+
+ but = QCheckBox("draw crystal marks")
+ but.setChecked(False)
+ self._draw_crystal_marks = False
+ but.stateChanged.connect(self.set_draw_crystal_marks)
+ bl.addWidget(but, 10, 1, 1, 1)
+
+
+ but = QPushButton("Transform")
+ but.clicked.connect(self.transform_non_fiducials_in_model)
+ bl.addWidget(but, 20, 0, 2, 2)
+
+ layout.addWidget(frame)
+ self.markersTable = QTableWidget()
+ self.markersTable.setSelectionMode(QAbstractItemView.SingleSelection)
+ self.markersTable.setSelectionBehavior(QAbstractItemView.SelectRows)
+ self.markersTable.setItemDelegate(MarkerDelegate(self))
+
+ # self.markersTable.horizontalHeader().setDefaultSectionSize(80)
+ self.markersTable.setColumnCount(5)
+ self.markersTable.setHorizontalHeaderLabels(("Fiducial?", "orig X", "orig Y", "X", "Y"))
+ hh=self.markersTable.horizontalHeader()
+ hh.setSectionResizeMode(0, QHeaderView.ResizeToContents)
+ hh.setSectionResizeMode(1, QHeaderView.ResizeToContents)
+ hh.setSectionResizeMode(2, QHeaderView.ResizeToContents)
+ hh.setSectionResizeMode(3, QHeaderView.ResizeToContents)
+ hh.setSectionResizeMode(4, QHeaderView.ResizeToContents)
+ hh.setSectionResizeMode(QHeaderView.Stretch)
+
+ self.markersTable.itemSelectionChanged.connect(self.selection_changed)
+
+ self.markersTable.setContextMenuPolicy(Qt.ActionsContextMenu)
+
+ deleteRowAction = QAction("Delete this row", self)
+ deleteRowAction.triggered.connect(self.delete_selected)
+ self.markersTable.addAction(deleteRowAction)
+
+ moveRequestAction = QAction("Move Here", self)
+ moveRequestAction.triggered.connect(self.request_stage_movement)
+ self.markersTable.addAction(moveRequestAction)
+
+ layout.addWidget(self.markersTable, stretch=2)
+ self.connect_all_signals()
+
+ #self._yamlFn=fn=os.path.expanduser('~/.config/PSI/SwissMX.yaml')
+
+ #self._tree=tree=pg.DataTreeWidget(data=self._data)
+ self._data=data
+ self._tree=tree=pg.DataTreeWidget(data=data)
+ layout.addWidget(tree, stretch=2)
+
+ def load_file(self, filename=None):
+ app = QApplication.instance()
+ #cfg = app._cfg
+ #logger = logging.getLogger("preloc.loadMarkers")
+ #def_folder = join(folders.pgroup_folder, "preloc_sheets") #ZAC: orig. code
+ #def_folder =''
+ #data_folder = cfg.value("folders/last_prelocation_folder", def_folder)
+ data_folder=''
+ if filename is None:
+ filename, _ = QFileDialog.getOpenFileName(self,"Load yaml data file",None,
+ "yaml files (*.yaml);;json files (*.json);;pickle files (*.pkl);;text files (*.txt);;all files (*)",)
+ if not filename: # cancelled dialog
+ return
+
+ ext=filename.rsplit('.',1)[1].lower()
+ if ext=='pkl':
+ with open(filename, 'rb') as f:
+ data=pickle.load(f)
+ if ext=='yaml':
+ with open(filename, 'r') as f:
+ data = yaml.load(f)
+ elif ext=='json':
+ with open(filename, 'r') as f:
+ data=json.load(f,object_hook=MyJsonDecoder)
+ print(data)
+ self._data=data
+ self._tree.setData(data)
+ return
+
+
+ try:
+ cfg.setValue("folders/last_prelocation_folder", QFileInfo(filename).canonicalPath())
+ cfg.setValue("folders/last_prelocation_sheet", QFileInfo(filename).absolutePath())
+ except TypeError as e:
+ pass
+
+ self.clearMarkers()
+
+ _log.info(f"loading prelocated coords from {filename}")
+ df = pd.read_csv(filename, comment="#", header=None, delim_whitespace=False, delimiter="[\t,;]")
+
+ try:
+ prefix = QFileInfo(filename).baseName()
+ except:
+ prefix = filename.name
+ filename = prefix
+
+ logger.info(f"prefix => {prefix}")
+
+ gonio_coords_available = True
+ for data in df.as_matrix(): # FIXME FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
+ row = self.markersTable.rowCount()
+ self.markersTable.setRowCount(row + 1)
+ original = np.copy(data)
+ data = list(data)
+
+ if len(data) in [2, 3]:
+ gonio_coords_available = False
+ data.extend([0, 0])
+
+ if len(data) == 4:
+ data.insert(0, False) # fiducial flag
+ elif len(data) == 5: # fiducial already there, convert to bool
+ data[0] = bool(data[0])
+ else:
+ QMessageBox.warning(
+ self,
+ "Wrong number of points in data file",
+ "I was expecting either 2, 3, 4 or 5 data points per line."
+ "\n\nFailed around a line with: {}".format(list(original)),
+ )
+
+ self.addSingleMarker(row, data)
+
+ self._xtals_transformed = False
+ self.prefixSelected.emit(prefix)
+
+ # only emit this signal if goniometer coordinates already read from file
+ if gonio_coords_available:
+ logger.debug(f"dataFileLoaded.emit => {filename}")
+ self.dataFileLoaded.emit(filename)
+
+ def save_file(self, filename=None):
+ # filename = folders.get_file("prelocated-save.dat")
+ #data_folder = settings.value("folders/last_prelocation_folder")
+ data_folder=''
+ if filename is None:
+ filename, _ = QFileDialog.getSaveFileName(self,"Soad yaml data file",data_folder,
+ "yaml files (*.yaml);;json files (*.json);;pickle files (*.pkl);;text files (*.txt);;all files (*)",)
+ if not filename:
+ return
+
+ #settings.setValue("folders/last_prelocation_folder", QFileInfo(filename).canonicalPath())
+ #_log.info("Saving data in {}".format(filename))
+ #data = self.get_data(as_numpy=True)
+ #df = pd.DataFrame(data)
+ #df.to_csv(filename, float_format="%.6f")
+ #import numpy as np
+ ext=filename.rsplit('.',1)[1].lower()
+ if ext=='pkl':
+ with open(filename, 'wb') as f:
+ pickle.dump(self._data, f)
+ elif ext=='yaml':
+ with open(filename, 'w') as f:
+ yaml.dump(self._data, f)
+ elif ext=='json':
+ with open(filename, 'w') as f:
+ json.dump(self._data, f,cls=MyJsonEncoder)
+ print(self._data)
+
+ def add_obj(self):
+ _log.warning("TODO: IMPLEMENT")
+
+ def delete_selected(self):
+ row = self._current_row
+ try:
+ row += 0
+ except:
+ _log.warning("select a row first")
+ QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",)
+ return
+ self.markersTable.removeRow(row)
+ self.prelocatedDataUpdated.emit()
+
+ def selection_changed(self):
+ row = self.markersTable.currentRow()
+ if row < 0:
+ return
+ self._current_row = row
+ self.selectedRowChanged.emit(row)
+ _log.debug("selection changed: current row {}".format(row))
+
+ def connect_all_signals(self):
+ self.prefixSelected.connect(lambda t: self._label_prefix.setText(t))
+ self.dataFileLoaded.connect(lambda t: self._label_datafile.setText(t))
+
+ def markerItemChanged(self, item):
+ print(item.row(), item.column())
+
+ def set_fiducial_coords(self, camx, camy, gx, gy):
+ tbl = self.markersTable
+ row = self._current_row
+ try:
+ row += 0
+ except:
+ _log.warning("select a row first")
+ QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",)
+ return
+
+ origx = tbl.item(row, ORIGINAL_X).data(RoleOriginalCoord_X)
+ origy = tbl.item(row, ORIGINAL_Y).data(RoleOriginalCoord_Y)
+
+ item = tbl.item(row, DATA_ITEM)
+ item.setData(RoleCameraCoord_X, camx)
+ item.setData(RoleCameraCoord_Y, camy)
+ item.setData(RoleGoniometerCoord_X, gx)
+ item.setData(RoleGoniometerCoord_Y, gy)
+ # item.setData(RoleOriginalCoord_X, origx)
+ # item.setData(RoleOriginalCoord_Y, origy)
+ # logger.debug(': [{}] = Original: {}, {} | Camera: {}, {} | Gonio: {}, {}'.format(1+row, origx, origy, camx, camy, gx, gy))
+ logger.debug(f": [{1+row}] = Original: {origx}, {origy} | Camera: {camx}, {camy} | Gonio: {gx}, {gy}")
+
+ tbl.item(row, GONIO_X).setData(Qt.DisplayRole, f"{gx:8.5f} mm\n{camx:8.1f} px")
+ tbl.item(row, GONIO_Y).setData(Qt.DisplayRole, f"{gy:8.5f} mm\n{camy:8.1f} px")
+
+ # mark row as a fiducial
+ tbl.item(row, 0).setCheckState(Qt.Checked)
+ tbl.item(row, 0).setData(Qt.UserRole, True)
+ tbl.selectRow(row + 1)
+ self.prelocatedDataUpdated.emit()
+
+ def set_selected_gonio_coords(self, xy):
+ tbl = self.markersTable
+ row = self._current_row
+ try:
+ row += 0
+ except:
+ _log.warning("select a row first")
+ QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",)
+ return
+
+ for n, v in enumerate(xy):
+ idx = 3 + n
+ tbl.setCurrentCell(row, idx) # gonio X
+ _log.debug("item: [{},{}] = {}".format(row, idx, v))
+ item = tbl.currentItem()
+ item.setData(Qt.EditRole, "{:.3f}".format(v))
+ item.setData(Qt.UserRole, v)
+
+ # mark row as a fiducial
+ tbl.setCurrentCell(row, 0)
+ tbl.currentItem().setCheckState(Qt.Checked)
+ tbl.currentItem().setData(Qt.UserRole, True)
+
+ def set_data_goniometer(self, row: int, xy):
+ tbl = self.markersTable
+
+ row = self._current_row
+ try:
+ row += 0
+ except:
+ _log.warning("select a row first")
+ QMessageBox.warning(self,"Select a marker first","You must select a marker first before updating its goniometer position",)
+ return
+
+ for n, v in enumerate(xy):
+ idx = 3 + n
+ tbl.setCurrentCell(row, idx) # gonio X
+ item = tbl.currentItem()
+ item.setData(Qt.EditRole, "{:.3f}".format(v))
+ item.setData(Qt.UserRole, v)
+
+ @pyqtSlot(int)
+ def set_draw_crystal_marks(self, val):
+ logger.info(f"{'' if val else 'not '}drawing crystal markers")
+ self._draw_crystal_marks = val
+
+ @pyqtSlot(int)
+ def set_collect_fiducials(self, val):
+ logger.info(f"{'' if val else 'not '}collecting fiducials")
+ self._collect_fiducials = val
+
+ def get_collection_targets(self):
+ return self.get_data(fiducials=self._collect_fiducials)
+
+
+ def get_data(self, fiducials=True, crystals=True, as_numpy=False):
+ """return a list of tuples with all defined data
+
+ [
+ (is_fiducial(boolean), x, y, gonio_x, gonio_y),...
+ ]
+ """
+ data = []
+ item = self.markersTable.item
+
+ for row in range(self.markersTable.rowCount()):
+ is_fiducial = Qt.Checked == item(row, 0).checkState()
+ ditem = item(row, DATA_ITEM)
+
+ x = ditem.data(RoleGoniometerCoord_X)
+ y = ditem.data(RoleGoniometerCoord_Y)
+ cx = ditem.data(RoleCameraCoord_X)
+ cy = ditem.data(RoleCameraCoord_Y)
+ origx = ditem.data(RoleOriginalCoord_X)
+ origy = ditem.data(RoleOriginalCoord_Y)
+
+ if is_fiducial and fiducials:
+ data.append([is_fiducial, x, y, cx, cy, origx, origy])
+ if not is_fiducial and crystals:
+ data.append([is_fiducial, x, y, cx, cy, origx, origy])
+
+ if as_numpy:
+ data = np.asarray(data)
+ return data
+
+ def get_original_coordinates(self, fiducials=True):
+ """return a numpy array with the original prelocated coordinates of the fiducial entries"""
+ data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True)
+ data = data[:, 5:]
+ zeros = np.zeros(data.shape)
+ zeros[:, 1] = 1
+ return np.concatenate((data, zeros), axis=1)
+
+ def get_goniometer_coordinates(self, fiducials=True):
+ """return a numpy array with the goniometer coordinates of the fiducial entries"""
+ data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True)
+ data = data[:, 1:3]
+ zeros = np.zeros(data.shape)
+ zeros[:, 1] = 1
+ return np.concatenate((data, zeros), axis=1)
+
+ def get_camera_coordinates(self, fiducials=True):
+ """return a numpy array with the goniometer coordinates of the fiducial entries"""
+ data = self.get_data(fiducials=fiducials, crystals=not fiducials, as_numpy=True)
+ data = data[:, 3:5]
+ zeros = np.zeros(data.shape)
+ zeros[:, 1] = 1
+ return np.concatenate((data, zeros), axis=1)
+
+ def dump_matrix(self, M):
+ scale, shear, angles, translate, perspective = tfs.decompose_matrix(M)
+ angles_deg = [a * 180 / np.pi for a in angles]
+
+ print("Transformation matrix Aerotech => SwissMX")
+ print(M)
+ print((" scale {:9.4f} {:9.4f} {:9.4f}".format(*scale)))
+ print((" shear {:9.4f} {:9.4f} {:9.4f}".format(*shear)))
+ print((" angles rad {:9.4f} {:9.4f} {:9.4f}".format(*angles)))
+ print((" angles deg {:9.4f} {:9.4f} {:9.4f}".format(*angles_deg)))
+ print((" translate {:9.4f} {:9.4f} {:9.4f}".format(*translate)))
+ print(("perspective {:9.4f} {:9.4f} {:9.4f}".format(*perspective)))
+
+ def transform_non_fiducials_in_model(self):
+ forg = self.get_original_coordinates(fiducials=True)
+ fgon = self.get_goniometer_coordinates(fiducials=True)
+ fcam = self.get_camera_coordinates(fiducials=True)
+
+ gmat = sort_cmass_3d(fgon)
+ omat = sort_cmass_3d(forg)
+
+ try:
+ M_org2gon = tfs.superimposition_matrix(omat.T, gmat.T, scale=True)
+ M_org2cam = tfs.superimposition_matrix(forg.T, fcam.T, scale=True)
+ except:
+ QMessageBox.warning(self.parent(), title="failed to find superimposition matrix", text="Failed to find superimposition matrix.\n\tPlease try again.")
+ return
+
+ # scale, shear, angles, translate, perspective = tfs.decompose_matrix(M_org2gon)
+
+ org_data = self.get_original_coordinates(fiducials=False)
+
+ gon_data = np.dot(M_org2gon, org_data.T)
+ cam_data = np.dot(M_org2cam, org_data.T)
+
+ tbl = self.markersTable
+ num_fiducials = forg.shape[0]
+
+ gon_data = (gon_data.T)[:, 0:2] # only X,Y matters
+ cam_data = (cam_data.T)[:, 0:2] # only X,Y matters
+ combined = np.concatenate((gon_data, cam_data), axis=1)
+
+ item = self.markersTable.item # function alias
+ for row, data in enumerate(
+ combined, num_fiducials
+ ): # enumeration starts at *num_fiducials*
+ gx, gy, cx, cy = data
+ ditem = item(row, DATA_ITEM)
+ ditem.setData(RoleCameraCoord_X, cx)
+ ditem.setData(RoleCameraCoord_Y, cy)
+ ditem.setData(RoleGoniometerCoord_X, gx)
+ ditem.setData(RoleGoniometerCoord_Y, gy)
+ item(row, GONIO_X).setData(Qt.DisplayRole, f"{gx:8.5f} mm\n{cx:8.1f} px")
+ item(row, GONIO_Y).setData(Qt.DisplayRole, f"{gy:8.5f} mm\n{cy:8.1f} px")
+ self._xtals_transformed = True
+ self.prelocatedDataUpdated.emit()
+
+ def request_stage_movement(self):
+ logger = logging.getLogger("preloc.move_stage")
+ row = self._current_row
+ x = self.markersTable.item(row, DATA_ITEM).data(RoleGoniometerCoord_X)
+ y = self.markersTable.item(row, DATA_ITEM).data(RoleGoniometerCoord_Y)
+ logger.info(f"request move gonio to {x:.3f}, {y:.3f} mm")
+ self.moveFastStageRequest.emit(x, y)
+
+ def dump_numpy(self):
+ R = tfs.random_rotation_matrix(np.random.random(3))
+ d = self.get_goniometer_coordinates()
+ print("dumping")
+ print(d)
+ dR = np.dot(R, d)
+ print(dR.T)
+
+ def get_fiducials(self, as_numpy=False):
+ return self.get_data(crystals=False, as_numpy=as_numpy)
+
+ def get_crystals(self, as_numpy=False):
+ return self.get_data(fiducials=False, as_numpy=as_numpy)
+
+ def dump_data(self):
+ print(self._data)
+ #for ref, x, y, cx, cy, ox, oy in self.get_data():
+ # print(f"fiducial:{ref} [{x}, {y}, {cx}, {cy}]")
+
+ def append_data(self, data):
+ """append data (a list of values) to the model
+
+ len(data) == 2 => (X, Y) from prelocated coordinate
+ len(data) == 4 => (X, Y, GX, GY) plus gonio coordinates
+ len(data) == 5 => (fiducial?, X, Y, GX, GY) plus fiducial
+ """
+ row = self.markersTable.rowCount()
+ self.markersTable.setRowCount(row + 1)
+
+ data = list(data)
+ if len(data) == 2:
+ data.extend([0, 0])
+ if len(data) == 4:
+ data.insert(0, False) # fiducial flag
+ self.addSingleMarker(row, data)
+ self.prelocatedDataUpdated.emit()
+
+ def clearMarkers(self):
+ self.markersTable.setRowCount(0)
+ self.markersDeleted.emit()
+
+ def generate_random_data(self):
+ import io
+
+ data = io.StringIO()
+ npoints = self._num_random_points.value()
+ for n in range(npoints):
+ x, y, a, b = (
+ np.random.randint(0, 2000),
+ np.random.randint(0, 2000),
+ np.random.randint(-4000, 4000) / 1000.,
+ np.random.randint(-4000, 4000) / 1000.,
+ )
+ data.write("{}\t{}\t{}\t{}\n".format(x, y, a, b))
+ data.seek(0)
+ data.name = "random.csv"
+ self.loadMarkers(data)
+
+
+
+
+ def addSingleMarker(self, row, marker):
+ is_fiducial, origx, origy, gx, gy = marker
+ _log.debug(f": [{1+row}] | Original: {origx}, {origy} | Gonio: {gx}, {gy}")
+ item0 = QTableWidgetItem()
+ item0.setData(Qt.UserRole, is_fiducial)
+ item0.setFlags(item0.flags() & ~Qt.ItemIsEditable)
+ item0.setCheckState(Qt.Checked if is_fiducial else Qt.Unchecked)
+ item0.setTextAlignment(Qt.AlignCenter)
+
+ item1 = QTableWidgetItem("{:.1f}".format(origx))
+ item1.setTextAlignment(Qt.AlignRight)
+
+ item2 = QTableWidgetItem("{:.1f}".format(origy))
+ item2.setTextAlignment(Qt.AlignRight)
+
+ item3 = QTableWidgetItem("{:.3f} mm".format(gx))
+ item3.setFlags(item3.flags() & ~Qt.ItemIsEditable)
+ item3.setTextAlignment(Qt.AlignRight)
+
+ item4 = QTableWidgetItem("{:.3f} mm".format(gy))
+ item4.setFlags(item4.flags() & ~Qt.ItemIsEditable)
+ item4.setTextAlignment(Qt.AlignRight)
+
+ self.markersTable.setItem(row, 0, item0)
+ self.markersTable.setItem(row, ORIGINAL_X, item1)
+ self.markersTable.setItem(row, ORIGINAL_Y, item2)
+ self.markersTable.setItem(row, GONIO_X, item3)
+ self.markersTable.setItem(row, GONIO_Y, item4)
+ self.markerAdded.emit(is_fiducial, [origx, origy, gx, gy])
+
+ item = self.markersTable.item(row, DATA_ITEM)
+ item.setData(RoleCameraCoord_X, origx) # initially set to original
+ item.setData(RoleCameraCoord_Y, origy) # initially set to original
+ item.setData(RoleGoniometerCoord_X, gx)
+ item.setData(RoleGoniometerCoord_Y, gy)
+ item.setData(RoleOriginalCoord_X, origx)
+ item.setData(RoleOriginalCoord_Y, origy)
+
+"""
+Signals QTableWidget
+void cellActivated(int row, int column)
+void cellChanged(int row, int column)
+void cellClicked(int row, int column)
+void cellDoubleClicked(int row, int column)
+void cellEntered(int row, int column)
+void cellPressed(int row, int column)
+void currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
+void currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
+void itemActivated(QTableWidgetItem *item)
+void itemChanged(QTableWidgetItem *item)
+void itemClicked(QTableWidgetItem *item)
+void itemDoubleClicked(QTableWidgetItem *item)
+void itemEntered(QTableWidgetItem *item)
+void itemPressed(QTableWidgetItem *item)
+void itemSelectionChanged()
+"""
+
+if __name__ == "__main__":
+ import sys
+ import pyqtUsrObj as UsrGO
+
+ class Monster(yaml.YAMLObject):
+ yaml_tag = u'!Monster'
+ def __init__(self, name, hp, ac, attacks):
+ self.name = name
+ self.hp = hp
+ self.ac = ac
+ self.attacks = attacks
+ def __repr__(self):
+ return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
+ self.__class__.__name__, self.name, self.hp, self.ac, self.attacks)
+
+
+ class MainWindow(QMainWindow):
+ def __init__(self, parent=None):
+ super(MainWindow, self).__init__(parent)
+ self.move(100,100)
+ self.resize(500,800)
+ self.centralWidget=QWidget()
+ self.wndFixTrg=WndFixTarget(parent=self)
+
+ self.setCentralWidget(self.centralWidget)
+ mainLayout=QVBoxLayout()
+ mainLayout.addWidget(self.wndFixTrg)
+ self.centralWidget.setLayout(mainLayout)
+
+
+ app = QApplication(sys.argv)
+ mainWin = MainWindow()
+
+ t=3
+ if t==0:
+ data=[{'name':'John Doe', 'occupation':'gardener'}, {'name':'Lucy Black', 'occupation':'teacher'}]
+ elif t==1:
+ data ={
+ 'name': 'John Doe', 'occupation': 'gardener',
+ 'A':(1,2,3),
+ 'B':[1,2,3],
+ 'C':{1,2,3},
+ 'D':
+ {'1':(1,2,3),
+ '2':'df',
+ '3':'dddd'},
+ '4':np.array(((1,2,3),(4,5,6))),
+ '5':np.array(range(40*2)).reshape(-1,2)
+ }
+ elif t==2:
+ data = {
+ 'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
+ 'a dict': {
+ 'x': 1,
+ 'y': 2,
+ 'z': 'three'
+ },
+ 'an array': np.random.randint(10, size=(40,10)),
+ #'a traceback': some_func1(),
+ #'a function': some_func1,
+ #'a class': pg.DataTreeWidget,
+ }
+ elif t==3:
+ data=[
+ UsrGO.Grid(pos=(120.0, -100.0), size=(1000.0, 500.0), cnt=(10, 5), ficucialScale=2),
+ Monster(name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT'])
+ ]
+ mainWin.wndFixTrg._data=data
+ mainWin.wndFixTrg._tree.setData(data)
+ mainWin.show()
+ sys.exit(app.exec_())
diff --git a/camera.py b/camera.py
index 09e46dc..f7385ff 100755
--- a/camera.py
+++ b/camera.py
@@ -248,9 +248,9 @@ class epics_cam(object):
hr=h//4
imgSeq[:,0:hr,0:wr]+=np.random.randint(0,100,(t,hr,wr),dtype=np.uint16)
elif mode==1:
- import glob,PIL.Image
+ import os,glob,PIL.Image
#path='/home/zamofing_t/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python/SwissMX/simCamImg/*.png'
- path='simCamImg/*.png'
+ path=os.path.join(os.path.dirname(__file__),'simCamImg/*.png')
_log.info('generate simulation images:{}...'.format(path))
glb=glob.glob(path)
img = PIL.Image.open(glb[0])
diff --git a/pyqtUsrObj.py b/pyqtUsrObj.py
index c468014..eb521ff 100644
--- a/pyqtUsrObj.py
+++ b/pyqtUsrObj.py
@@ -138,6 +138,27 @@ class Marker(pg.ROI):
# p.drawRect(-w / 2, -h / 2, w, h)
# # p.drawText(-w, -h, '{:.0f}x{:.0f}'.format(*self._size))
+class Fiducial(pg.ROI):
+ def __init__(self, pos, size, xyz, **kargs):
+ pg.ROI.__init__(self, pos, size, **kargs)
+ self._xyz=xyz
+
+ def paint(self, p, *args):
+ #pg.ROI.paint(self, p, *args)
+ r=QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
+ p.setRenderHint(QtGui.QPainter.Antialiasing)
+ p.setPen(self.currentPen)
+ p.translate(r.left(), r.top())
+ p.scale(.01*r.width(), .01*r.height()) # -> values x,y 0 to 100
+ p.drawRect(0, 0, 100, 100)
+ p.setPen(pg.mkPen(width=1, color=[255, 0, 0]))
+ p.drawLine(pg.Point(50,10), pg.Point(50, 90))
+ p.drawLine(pg.Point(10,50), pg.Point(90, 50))
+ tr=p.transform()
+ tr.setMatrix(tr.m11(), tr.m12(), tr.m13(), tr.m21(), -tr.m22(), tr.m23(), tr.m31(), tr.m32(), tr.m33())
+ p.setTransform(tr)
+
+
class Grid(pg.ROI):
'''a grid'''
@@ -200,6 +221,25 @@ class Grid(pg.ROI):
#p.drawEllipse(0, 0, 1, 1)
#p.drawRect(0, 0, 1, 1)
+ def __repr__(self):
+ s=f'{self.__class__.__name__}:(pos:{tuple(self.pos())}, size:{tuple(self.size())}, cnt:{self._cnt}, ficucialScale:{self._fidScl}}}'
+ return s
+import yaml
+def yaml_repr_Grid(dumper, data):
+ #s=f'{{"pos":{tuple(data.pos())}, "size":{tuple(data.size())}, "cnt":{data._cnt}, "ficucialScale":{data._fidScl}}}'
+ #return dumper.represent_scalar(u'!Grid', s)
+ m={'pos':tuple(data.pos()),'size':tuple(data.size()),'cnt':data._cnt, 'ficucialScale':data._fidScl}
+ return dumper.represent_mapping(u'!Grid', m)
+yaml.add_representer(Grid, yaml_repr_Grid)
+
+def yaml_cnstr_Grid(loader, node):
+ #value = loader.construct_scalar(node)
+ #kwargs=eval(value)
+ #return Grid(**kwargs)
+ m = loader.construct_mapping(node)
+ return Grid(**m)
+yaml.add_constructor(u'!Grid', yaml_cnstr_Grid)
+
class Path(pg.ROI):
'''
@@ -524,6 +564,10 @@ if __name__=='__main__':
vi=FixTargetFrame((400,-200),(400,400),tpl='12.5x12.5')
vb.addItem(vi)
+ vi=Fiducial((0,200),(40,40),(1,2,3))
+ vb.addItem(vi)
+
+
childTree(vb)
w.scene().sigMouseClicked.connect(mouse_click_event)
diff --git a/swissmx.py b/swissmx.py
index 3311e8d..74b2bc8 100755
--- a/swissmx.py
+++ b/swissmx.py
@@ -69,6 +69,8 @@ TASK_PRELOCATED = "prelocated"
TASK_HELICAL = "helical"
TASK_EMBL = "embl"
ts.log('Import part 2/8:')
+
+import ModuleFixTarget
import PrelocatedCoordinatesModel # ZAC: orig. code
from EmblModule import EmblWidget #ZAC: orig. code
from HelicalTable import HelicalTableWidget #ZAC: orig. code
@@ -644,6 +646,19 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
camera.epics_cam.set_fiducial(pic, 255)
self._goImg.setImage(pic)
+ def new_frame_sim_cb(self, **kwargs):
+ app=QApplication.instance()
+ sim=app._camera._sim
+ imgSeq=sim['imgSeq']
+ idx=sim['imgIdx']
+ sim['imgIdx']=(idx+1)%imgSeq.shape[0]
+ # _log.info('simulated idx:{}'.format(idx))
+ pic=imgSeq[idx]
+ self._goImg.setImage(pic)
+
+ delay=500 # ms -> 2fps
+ QtCore.QTimer.singleShot(delay, self.new_frame_sim_cb)
+
def load_stylesheet(self):
with open("swissmx.css", "r") as sheet:
self.setStyleSheet(sheet.read())
@@ -673,11 +688,11 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
toolbox = QToolBox()
layout.addWidget(toolbox)
- self.build_faststage_group(toolbox)
+ self.build_group_faststage(toolbox)
# self.build_slits_group(toolbox)
- self.build_collimator_group(toolbox)
- self.build_posttube_group(toolbox)
- self.build_xeye_group(toolbox)
+ self.build_group_collimator(toolbox)
+ self.build_group_posttube(toolbox)
+ self.build_group_xeye(toolbox)
#self.build_cryo_group(toolbox)
# monitor all axis for an axis fault
@@ -801,7 +816,7 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
msg = ""
self._message_critical_fault.setText(msg)
- def build_faststage_group(self, toolbox):
+ def build_group_faststage(self, toolbox):
qutilities.add_item_to_toolbox(toolbox,"Fast Stage",
widget_list=[
# self.get_tweaker('SAR-EXPMX:MOT_BLGT', alias='backlight', label='backlight'),
@@ -813,7 +828,7 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
],
)
- def build_collimator_group(self, toolbox):
+ def build_group_collimator(self, toolbox):
qutilities.add_item_to_toolbox(toolbox,"Collimator",
widget_list=[
self.get_tweaker("SARES30-ESBMX1", alias="colli_x", label="colli X", mtype="smaract_motor",),
@@ -821,7 +836,7 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
],
)
- def build_posttube_group(self, toolbox):
+ def build_group_posttube(self, toolbox):
widgets = [
self.get_tweaker("SARES30-ESBMX4", alias="tube_usx", label="upstream X", mtype="smaract_motor",),
self.get_tweaker("SARES30-ESBMX6", alias="tube_usy", label="upstream Y", mtype="smaract_motor",),
@@ -851,7 +866,7 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
block.layout().addStretch()
toolbox.addItem(block, label)
- def build_xeye_group(self, toolbox):
+ def build_group_xeye(self, toolbox):
qutilities.add_item_to_toolbox(
toolbox,
"X-Ray Eye",
@@ -1509,12 +1524,33 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
exp_tab.layout().addStretch()
# DAQ Methods Tabs
+ self.build_tab_module_fix_target()
self._OLD_build_daq_methods_grid_tab()
self._OLD_build_daq_methods_prelocated_tab() #ZAC: orig. code
self._OLD_create_helical_widgets() #ZAC: orig. code
self._OLD_build_daq_methods_embl_tab()
#self._OLD_build_sample_selection_tab()
+ def build_tab_module_fix_target(self):
+ #tab = self._tab_daq_method_prelocated
+ data=self._goTracked['objLst']
+
+ self._moduleFixTarget =mft = ModuleFixTarget.WndFixTarget(self,data)
+ tab = self._tabs_daq_methods.insertTab(0,mft,'Fix Target')
+ mft._btnAdd.clicked.connect(self.module_fix_target_add_obj)
+
+
+ #tab.layout().addWidget(mft)
+ mft.prefixSelected.connect(lambda prefix: self._le_prefix.setText(prefix))
+ mft.dataFileLoaded.connect(self._OLD_daq_method_prelocated_update_markers)
+ mft.prelocatedDataUpdated.connect(self._OLD_daq_method_prelocated_update_markers)
+ mft.markersDeleted.connect(self._OLD_daq_method_prelocated_remove_markers)
+ mft.moveFastStageRequest.connect(self._OLD_move_fast_stage)
+ self.fiducialPositionSelected.connect(self._OLD_daq_method_prelocated_set_fiducial)
+ self.appendPrelocatedPosition.connect(self._OLD_daq_method_prelocated_append_data)
+ #self._preloc_inspect_area = QPlainTextEdit()
+ #tab.layout().addWidget(self._preloc_inspect_area)
+
def switch_task(self, index=0):
stack = self._centerpiece_stack
@@ -1573,8 +1609,42 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
del app._raw_opt_ctr
return
+ def module_fix_target_add_obj(self,*args,**kwargs):
+ mft=self._moduleFixTarget
+ fx=self.tweakers["fast_x"].get_rbv()
+ fy=self.tweakers["fast_y"].get_rbv()
+ #cz=self.tweakers["fast_y"].get_rbv()
+ app=QApplication.instance()
+ geo=app._geometry
+ oc=geo._opt_ctr
+ idx=mft._cbType.currentIndex()
+ param=mft._txtParam.text()
-# **************** OBSOLETE AND/OR OLD STUFF ****************
+ #cb.addItems(["Fiducial", "FixTarget(12.5x12.5)", "FixTarget(23.0x23.0)", "FixTarget()", "Grid()"])
+
+ if idx==0:
+ go=UsrGO.Fiducial((120, -100), (200, 150),(1,2,3))
+ elif idx==1:
+ v=geo.pos2pix((12.5, 0))
+ l=np.linalg.norm(v)
+ go=UsrGO.FixTargetFrame(-oc, (l, l), tpl='12.5x12.5')
+ elif idx==2:
+ v=geo.pos2pix((23, 0))
+ l=np.linalg.norm(v)
+ go=UsrGO.FixTargetFrame(-oc, (l, l), tpl='23.0x23.0')
+ elif idx==3:
+ go=UsrGO.FixTargetFrame((120, -100), (200, 150), tpl='test')
+ elif idx==4:
+ go=UsrGO.Grid((120, -100), (200, 150), (30, 22), 2)
+ else:
+
+ _log.error('set xstep 0..2 for tests')
+ self.vb.addItem(go)
+ obj=self._goTracked['objLst']
+ obj.append(go)
+ mft._tree.setData(obj)
+
+ # **************** OBSOLETE AND/OR OLD STUFF ****************
# functions are prefixed with _OLD_
@@ -1677,19 +1747,6 @@ class SwissMxWnd(QMainWindow, Ui_MainWindow):
app=QApplication.instance()
app._camera.pause()
- def _OLD_new_frame_sim_cb(self, **kwargs):
- app=QApplication.instance()
- sim=app._camera._sim
- imgSeq=sim['imgSeq']
- idx=sim['imgIdx']
- sim['imgIdx']=(idx+1)%imgSeq.shape[0]
- # _log.info('simulated idx:{}'.format(idx))
- pic=imgSeq[idx]
- self._goImg.setImage(pic)
-
- delay=500 # ms -> 2fps
- QtCore.QTimer.singleShot(delay, self.new_frame_sim_cb)
-
def _OLD_init_settings_tracker(self):
app=QApplication.instance()
cfg=app._cfg