#!/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 json, base64 #, os, pickle, yaml import numpy as np import pyqtUsrObj as UsrGO 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) elif type(obj) not in (dict,list,str,int): try: return obj.obj2json(self) except AttributeError: _log.error('dont know how to json') return repr(obj) return json.JSONEncoder.default(self, obj) def MyJsonDecoder(dct): if isinstance(dct, dict): if '__class__' in dct: cls=dct.pop('__class__') cls=UsrGO.__dict__[cls] obj=cls.__new__(cls) obj.__init__(**dct) #try: # obj.json2obj(dct) #except AttributeError: # obj.__init__(**dct) return obj elif '__ndarray__' in dct: data = base64.b64decode(dct['__ndarray__']) return np.frombuffer(data[:-1], dct['dtype']).reshape(dct['shape']) return dct 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(lambda x: _log.warning("TODO: IMPLEMENT") ) bl.addWidget(btnAdd, 1, 2,1,1) self._btnDelAll=btnDelAll = QPushButton("del all") #btnDelAll.clicked.connect(lambda x: _log.warning("TODO: IMPLEMENT") ) bl.addWidget(btnDelAll, 2, 2,1,1) self._btnFit=btnFit = QPushButton("fit with fiducials") #btnDelAll.clicked.connect(lambda x: _log.warning("TODO: IMPLEMENT") ) bl.addWidget(btnFit, 2, 1,1,1) # but = QPushButton("Dump to console") # but.clicked.connect(self.dump_data) # bl.addWidget(but, 10, 0, 1, 1) but = QCheckBox("collect fiducials") but.setChecked(False) but.setToolTip("Collect or not the fiducial positions.") self._collect_fiducials = False but.stateChanged.connect(self.set_collect_fiducials) bl.addWidget(but, 10, 0, 1, 1) but = QCheckBox("draw crystal marks") but.setChecked(False) self._draw_crystal_marks = False but.stateChanged.connect(self.set_draw_crystal_marks) bl.addWidget(but, 10, 1, 1, 1) but = QPushButton("Transform") but.clicked.connect(self.transform_non_fiducials_in_model) bl.addWidget(but, 20, 0, 2, 2) layout.addWidget(frame) self.markersTable = QTableWidget() self.markersTable.setSelectionMode(QAbstractItemView.SingleSelection) self.markersTable.setSelectionBehavior(QAbstractItemView.SelectRows) self.markersTable.setItemDelegate(MarkerDelegate(self)) # self.markersTable.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 data file",None, "json files (*.json);;all files (*)",) #"json files (*.json);;yaml files (*.yaml);;pickle files (*.pkl);;text files (*.txt);;all files (*)",) if not filename: # cancelled dialog return ext=filename.rsplit('.',1)[1].lower() if ext=='json': with open(filename, 'r') as f: data=json.load(f,object_hook=MyJsonDecoder) else: raise(IOError('unsupported file type')) #elif ext=='yaml': # with open(filename, 'r') as f: # data = yaml.load(f) #elif ext=='pkl': # with open(filename, 'rb') as f: # data=pickle.load(f) self._data=data self._tree.setData(data) try: wnd=app._mainWnd except AttributeError: _log.info('_mainWnd not handeled') pass else: for go in data: wnd.vb.addItem(go) wnd._goTracked['objLst']=self._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,"Save data file",data_folder, "json files (*.json);;all files (*)",) #"json files (*.json);;yaml files (*.yaml);;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=='json': with open(filename, 'w') as f: json.dump(self._data, f,cls=MyJsonEncoder, indent=2)#separators=(',', ':') else: raise(IOError('unsupported file type')) #elif ext=='yaml': # with open(filename, 'w') as f: # yaml.dump(self._data, f) #elif ext=='pkl': # with open(filename, 'wb') as f: # pickle.dump(self._data, f) #print(self._data) 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 class Monster: 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_())