class PvTunerDlg(QDialog): COL = 6 COL_ELEMENT = 0 COL_FIELD = 1 COL_PV = 2 COL_STEPSIZE = 3 COL_READBACK = 4 COL_SETPOINT = 5 FMT_READBACK = "%.4e" def __init__(self, parent=None): super(PvTunerDlg, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) #self.inputBox = QLineEdit("PL2G2C01A.x") #self.inputBox = QLineEdit("CXH2G6C01B.x") self.inputBox = QLineEdit("PL2G2C01A") addPvBtn = QPushButton("add") self.table = QTableWidget(0, PvTunerDlg.COL) self.table.setHorizontalHeaderLabels( ["Element", "Field", "PV", "Stepsize", "Readback", "setpoint"]) #self.table.horizontalHeader().setStretchLastSection(True) #self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) box = QGridLayout() box.addWidget(self.inputBox, 0, 0) box.addWidget(addPvBtn, 0, 1) box.addWidget(self.table, 1, 0, 1, 2) box.addWidget(buttonBox, 2, 0) self.setLayout(box) self.pvs_rb = [] self.pvs_rb_val_flat = [] self.pvs_sp = [] self.pvmoni = [] self.spinbox = [] self.connect(addPvBtn, SIGNAL("clicked()"), self.addPv) self.connect(buttonBox, SIGNAL("accepted()"), self.accept) #self.connect(self.table, SIGNAL("cellChanged"), self.updatePv) self.connect(buttonBox.button(QDialogButtonBox.Ok), SIGNAL("clicked()"), self.close) self.connect(self.table, SIGNAL("cellClicked(int, int)"), self._cell_clicked) def _cell_clicked(self, row, column): #print row, column if column in [self.COL_PV, self.COL_STEPSIZE]: item = self.table.item(row, column) if not item: return item.setFlags(item.flags() | Qt.ItemIsEditable) def _appendRecord(self, name): vec = name.split('.') if len(vec) > 2: QMessageBox.critical(None, "ERROR", "format is wrong") return if len(vec) == 1: elem, field = vec[0], 'value' elif len(vec) == 2: elem, field = vec elemobj = hla.getElements(elem) if elemobj: # pvsrb is a list pvsrb = elemobj.pv(field=field, handle='readback') self.pvs_rb.append(pvsrb) pvssp = elemobj.pv(field=field, handle='setpoint') self.pvs_sp.append(pvssp) else: QMessageBox.critical(None, "ERROR", "element %s not found" % elem) return # expand one row m, n = self.table.rowCount(), self.table.columnCount() self.table.insertRow(m) # add cells item = QTableWidgetItem(elem) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_ELEMENT, item) item = QTableWidgetItem(field) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_FIELD, item) item = QTableWidgetItem(', '.join(pvsrb)) #item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.table.setItem(m, self.COL_PV, item) readval = ['%.4e' % v for v in caget(pvsrb)] item = QTableWidgetItem(', '.join(readval)) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_READBACK, item) # set the stepsize of PV stepsize = 0.00001 if pvssp: item = QTableWidgetItem('%f' % stepsize) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_STEPSIZE, item) self.spinbox.append(QDoubleSpinBox(self.table)) self.spinbox[-1].setRange(-100, 100) #self.spinbox[-1].setValue(float(10.0)) self.spinbox[-1].setSingleStep(stepsize) self.spinbox[-1].setDecimals(10) self.spinbox[-1].valueChanged.connect(self._writePv) self.table.setCellWidget(m, self.COL_SETPOINT, self.spinbox[-1]) sp = float(caget(pvssp)[0]) #print "setpoint:", pvssp, sp, type(sp) self.spinbox[-1].setValue(-1e-5) #print "connected", self.spinbox[-1].value() else: item = self.table.item(m, self.COL_STEPSIZE) if item: item.setFlags(item.flags() & (~Qt.ItemIsEditable)) item = self.table.item(m, self.COL_SETPOINT) if item: item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.spinbox.append(None) self.table.resizeColumnsToContents() def addPv(self): self._appendRecord(str(self.inputBox.text())) self._updateMonitors() def minimalSizeHint(self): return QSize(800, 600) def _writePv(self, v): """ """ c = QObject.sender(self) i = self.spinbox.index(c) #print i, c.text(), "changed" #print self.pvs_sp[i], v caput(self.pvs_sp[i], v) def _updateMonitors(self): """ prepare the PV list for camonitor """ #print "Updating monitors" pvs = [] self.pvs_rb_val = [] for i in range(self.table.rowCount()): for j in range(len(self.pvs_rb[i])): self.pvs_rb_val.append([i, 0.0]) pvs.extend(self.pvs_rb[i]) for p in self.pvmoni: p.close() self.pvmoni = camonitor(pvs, self._updatePvValues) #print self.pvmoni #print pvs #print self.pvs_rb_val def _updatePvValues(self, val, idx): #print idx, val s = [] for i, v in enumerate(self.pvs_rb_val): if v[0] == self.pvs_rb_val[idx][0]: s.append(self.FMT_READBACK % val) #print s item = self.table.item(self.pvs_rb_val[idx][0], self.COL_READBACK) item.setText(','.join(s)) def closePvMonitors(self): #print "Closing PV Monitors" for p in self.pvmoni: p.close() pass def closeEvent(self, event): self.closePvMonitors() event.accept() def close(self): self.closePvMonitors() return True
class PvTunerDlg(QDialog): COL = 6 COL_ELEMENT = 0 COL_FIELD = 1 COL_PV = 2 COL_STEPSIZE = 3 COL_READBACK = 4 COL_SETPOINT = 5 FMT_READBACK = "%.4e" def __init__(self, parent=None): super(PvTunerDlg, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) #self.inputBox = QLineEdit("PL2G2C01A.x") #self.inputBox = QLineEdit("CXH2G6C01B.x") self.inputBox = QLineEdit("PL2G2C01A") addPvBtn = QPushButton("add") self.table = QTableWidget(0, PvTunerDlg.COL) self.table.setHorizontalHeaderLabels(["Element", "Field", "PV", "Stepsize", "Readback", "setpoint"]) #self.table.horizontalHeader().setStretchLastSection(True) #self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) box = QGridLayout() box.addWidget(self.inputBox, 0, 0) box.addWidget(addPvBtn, 0, 1) box.addWidget(self.table, 1, 0, 1, 2) box.addWidget(buttonBox, 2, 0) self.setLayout(box) self.pvs_rb = [] self.pvs_rb_val_flat = [] self.pvs_sp = [] self.pvmoni = [] self.spinbox = [] self.connect(addPvBtn, SIGNAL("clicked()"), self.addPv) self.connect(buttonBox, SIGNAL("accepted()"), self.accept) #self.connect(self.table, SIGNAL("cellChanged"), self.updatePv) self.connect(buttonBox.button(QDialogButtonBox.Ok), SIGNAL("clicked()"), self.close) self.connect(self.table, SIGNAL("cellClicked(int, int)"), self._cell_clicked) def _cell_clicked(self, row, column): #print row, column if column in [self.COL_PV, self.COL_STEPSIZE]: item = self.table.item(row, column) if not item: return item.setFlags(item.flags() | Qt.ItemIsEditable) def _appendRecord(self, name): vec = name.split('.') if len(vec) > 2: QMessageBox.critical(None, "ERROR", "format is wrong") return if len(vec) == 1: elem, field = vec[0], 'value' elif len(vec) == 2: elem, field = vec elemobj = hla.getElements(elem) if elemobj: # pvsrb is a list pvsrb = elemobj.pv(field=field, handle='readback') self.pvs_rb.append(pvsrb) pvssp = elemobj.pv(field=field, handle='setpoint') self.pvs_sp.append(pvssp) else: QMessageBox.critical(None, "ERROR", "element %s not found" % elem) return # expand one row m, n = self.table.rowCount(), self.table.columnCount() self.table.insertRow(m) # add cells item = QTableWidgetItem(elem) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_ELEMENT, item) item = QTableWidgetItem(field) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_FIELD, item) item = QTableWidgetItem(', '.join(pvsrb)) #item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.table.setItem(m, self.COL_PV, item) readval = ['%.4e' % v for v in caget(pvsrb)] item = QTableWidgetItem(', '.join(readval)) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_READBACK, item) # set the stepsize of PV stepsize = 0.00001 if pvssp: item = QTableWidgetItem('%f' % stepsize) item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.table.setItem(m, self.COL_STEPSIZE, item) self.spinbox.append(QDoubleSpinBox(self.table)) self.spinbox[-1].setRange(-100, 100) #self.spinbox[-1].setValue(float(10.0)) self.spinbox[-1].setSingleStep(stepsize) self.spinbox[-1].setDecimals(10) self.spinbox[-1].valueChanged.connect(self._writePv) self.table.setCellWidget(m, self.COL_SETPOINT, self.spinbox[-1]) sp = float(caget(pvssp)[0]) #print "setpoint:", pvssp, sp, type(sp) self.spinbox[-1].setValue(-1e-5) #print "connected", self.spinbox[-1].value() else: item = self.table.item(m, self.COL_STEPSIZE) if item: item.setFlags(item.flags() & (~Qt.ItemIsEditable)) item = self.table.item(m, self.COL_SETPOINT) if item: item.setFlags(item.flags() & (~Qt.ItemIsEditable)) self.spinbox.append(None) self.table.resizeColumnsToContents() def addPv(self): self._appendRecord(str(self.inputBox.text())) self._updateMonitors() def minimalSizeHint(self): return QSize(800, 600) def _writePv(self, v): """ """ c = QObject.sender(self) i = self.spinbox.index(c) #print i, c.text(), "changed" #print self.pvs_sp[i], v caput(self.pvs_sp[i], v) def _updateMonitors(self): """ prepare the PV list for camonitor """ #print "Updating monitors" pvs = [] self.pvs_rb_val = [] for i in range(self.table.rowCount()): for j in range(len(self.pvs_rb[i])): self.pvs_rb_val.append([i, 0.0]) pvs.extend(self.pvs_rb[i]) for p in self.pvmoni: p.close() self.pvmoni = camonitor(pvs, self._updatePvValues) #print self.pvmoni #print pvs #print self.pvs_rb_val def _updatePvValues(self, val, idx): #print idx, val s = [] for i, v in enumerate(self.pvs_rb_val): if v[0] == self.pvs_rb_val[idx][0]: s.append(self.FMT_READBACK % val) #print s item = self.table.item(self.pvs_rb_val[idx][0], self.COL_READBACK) item.setText(','.join(s)) def closePvMonitors(self): #print "Closing PV Monitors" for p in self.pvmoni: p.close() pass def closeEvent(self, event): self.closePvMonitors() event.accept() def close(self): self.closePvMonitors() return True
class NewRelationDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(self.tr("Nueva Relación")) vbox = QVBoxLayout(self) hbox = QHBoxLayout() self._line_relation_name = QLineEdit() hbox.addWidget(QLabel(self.tr("Nombre:"))) hbox.addWidget(self._line_relation_name) vbox.addLayout(hbox) vbox.addWidget(QLabel( self.tr("La primera fila corresponde a los campos"))) hbox = QHBoxLayout() btn_add_column = QPushButton(self.tr("Agregar Columna")) hbox.addWidget(btn_add_column) btn_add_tuple = QPushButton(self.tr("Agregar Tupla")) hbox.addWidget(btn_add_tuple) btn_remove_column = QPushButton(self.tr("Eliminar Columna")) hbox.addWidget(btn_remove_column) btn_remove_tuple = QPushButton(self.tr("Eliminar Tupla")) hbox.addWidget(btn_remove_tuple) vbox.addLayout(hbox) self._table = QTableWidget() vbox.addWidget(self._table) self._table.setRowCount(1) self._table.setColumnCount(2) self._table.setItem(0, 0, QTableWidgetItem("Campo 1")) self._table.setItem(0, 1, QTableWidgetItem("Campo 2")) hbox = QHBoxLayout() hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_ok = QPushButton(self.tr("Aceptar")) hbox.addWidget(btn_ok) btn_cancel = QPushButton(self.tr("Cancelar")) hbox.addWidget(btn_cancel) vbox.addLayout(hbox) # Connections self.connect(btn_add_column, SIGNAL("clicked()"), self.__add_column) self.connect(btn_remove_column, SIGNAL("clicked()"), self.__remove_column) self.connect(btn_add_tuple, SIGNAL("clicked()"), self.__add_tuple) self.connect(btn_remove_tuple, SIGNAL("clicked()"), self.__remove_tuple) self.connect(btn_ok, SIGNAL("clicked()"), self.__create_table) self.connect(btn_cancel, SIGNAL("clicked()"), self.close) def __add_column(self): columns = self._table.columnCount() self._table.insertColumn(columns) def __remove_column(self): current = self._table.currentColumn() self._table.removeColumn(current) def __add_tuple(self): tuples = self._table.rowCount() self._table.insertRow(tuples) def __remove_tuple(self): current = self._table.currentRow() self._table.removeRow(current) def __create_table(self): # Name of relation name = self._line_relation_name.text() if not name.strip(): QMessageBox.critical(self, self.tr("Error"), self.tr("Nombre de relación no especificado")) return rows = self._table.rowCount() columns = self._table.columnCount() rel = relation.Relation() # Header of relation fields = [] for i in range(columns): text = self._table.item(0, i).text() if not text.strip(): QMessageBox.critical(self, self.tr("Error"), self.tr("Nombre de campo inválido")) return fields.append(text) rel.fields = fields # Data data = {} for row in range(1, rows): reg = [] for column in range(columns): item = self._table.item(row, column) if item is None or not item.text().strip(): QMessageBox.critical(self, self.tr("Campo vacío"), self.tr("El campo {0}:{1} está " "vacío").format(row + 1, column + 1)) return reg.append(self._table.item(row, column).text()) data[row, column] = self._table.item(row, column).text() rel.insert(reg) # Add table and relation table_widget = Pireal.get_service("container").table_widget table_widget.add_table(rows - 1, columns, name, data, fields) table_widget.relations[name] = rel self.close()
class Main(plugin.Plugin): " Main Class " def initialize(self, *args, **kwargs): " Init Main Class " super(Main, self).initialize(*args, **kwargs) self.scriptPath, self.scriptArgs = "", [] self.profilerPath, self.tempPath = profilerPath, tempPath self.output = " ERROR: FAIL: No output ! " self.process = QProcess() self.process.finished.connect(self.on_process_finished) self.process.error.connect(self.on_process_error) self.tabWidget, self.stat = QTabWidget(), QWidget() self.tabWidget.tabCloseRequested.connect( lambda: self.tabWidget.setTabPosition(1) if self.tabWidget. tabPosition() == 0 else self.tabWidget.setTabPosition(0)) self.tabWidget.setStyleSheet('QTabBar{font-weight:bold;}') self.tabWidget.setMovable(True) self.tabWidget.setTabsClosable(True) self.vboxlayout1 = QVBoxLayout(self.stat) self.hboxlayout1 = QHBoxLayout() self.filterTableLabel = QLabel("<b>Type to Search : </b>", self.stat) self.hboxlayout1.addWidget(self.filterTableLabel) self.filterTableLineEdit = QLineEdit(self.stat) self.filterTableLineEdit.setPlaceholderText(' Type to Search . . . ') self.hboxlayout1.addWidget(self.filterTableLineEdit) self.filterHintTableLabel = QLabel(" ? ", self.stat) self.hboxlayout1.addWidget(self.filterHintTableLabel) self.vboxlayout1.addLayout(self.hboxlayout1) self.tableWidget = QTableWidget(self.stat) self.tableWidget.setAlternatingRowColors(True) self.tableWidget.setColumnCount(8) self.tableWidget.setRowCount(2) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(0, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(1, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(2, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(3, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(4, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(5, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(6, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(7, item) self.tableWidget.itemDoubleClicked.connect( self.on_tableWidget_itemDoubleClicked) self.vboxlayout1.addWidget(self.tableWidget) self.tabWidget.addTab(self.stat, " ? ") self.source = QWidget() self.gridlayout = QGridLayout(self.source) self.scintillaWarningLabel = QLabel( "QScintilla is not installed!. Falling back to basic text edit!.", self.source) self.gridlayout.addWidget(self.scintillaWarningLabel, 1, 0, 1, 2) self.sourceTreeWidget = QTreeWidget(self.source) self.sourceTreeWidget.setAlternatingRowColors(True) self.sourceTreeWidget.itemActivated.connect( self.on_sourceTreeWidget_itemActivated) self.sourceTreeWidget.itemClicked.connect( self.on_sourceTreeWidget_itemClicked) self.sourceTreeWidget.itemDoubleClicked.connect( self.on_sourceTreeWidget_itemClicked) self.gridlayout.addWidget(self.sourceTreeWidget, 0, 0, 1, 1) self.sourceTextEdit = QTextEdit(self.source) self.sourceTextEdit.setReadOnly(True) self.gridlayout.addWidget(self.sourceTextEdit, 0, 1, 1, 1) self.tabWidget.addTab(self.source, " ? ") self.result = QWidget() self.vlayout = QVBoxLayout(self.result) self.globalStatGroupBox = QGroupBox(self.result) self.hboxlayout = QHBoxLayout(self.globalStatGroupBox) self.totalTimeLcdNumber = QLCDNumber(self.globalStatGroupBox) self.totalTimeLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.totalTimeLcdNumber.setNumDigits(7) self.totalTimeLcdNumber.display(1000000) self.totalTimeLcdNumber.setFrameShape(QFrame.StyledPanel) self.totalTimeLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.totalTimeLcdNumber) self.tTimeLabel = QLabel("<b>Total Time (Sec)</b>", self.globalStatGroupBox) self.tTimeLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.tTimeLabel) self.numCallLcdNumber = QLCDNumber(self.globalStatGroupBox) self.numCallLcdNumber.setNumDigits(7) self.numCallLcdNumber.display(1000000) self.numCallLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.numCallLcdNumber.setFrameShape(QFrame.StyledPanel) self.numCallLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.numCallLcdNumber) self.numCallLabel = QLabel("<b>Number of calls</b>", self.globalStatGroupBox) self.numCallLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.numCallLabel) self.primCallLcdNumber = QLCDNumber(self.globalStatGroupBox) self.primCallLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.primCallLcdNumber.setFrameShape(QFrame.StyledPanel) self.primCallLcdNumber.setNumDigits(7) self.primCallLcdNumber.display(1000000) self.primCallLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.primCallLcdNumber) self.primCallLabel = QLabel("<b>Primitive calls (%)</b>", self.globalStatGroupBox) self.primCallLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.primCallLabel) self.vlayout.addWidget(self.globalStatGroupBox) try: from PyKDE4.kdeui import KRatingWidget self.rating = KRatingWidget(self.globalStatGroupBox) self.rating.setToolTip('Profiling Performance Rating') except ImportError: pass self.tabWidget.addTab(self.result, " Get Results ! ") self.resgraph = QWidget() self.vlayout2 = QVBoxLayout(self.result) self.graphz = QGroupBox(self.resgraph) self.hboxlayout2 = QHBoxLayout(self.graphz) try: from PyKDE4.kdeui import KLed KLed(self.graphz) except ImportError: pass self.hboxlayout2.addWidget( QLabel(''' Work in Progress :) Not Ready Yet''')) self.vlayout2.addWidget(self.graphz) self.tabWidget.addTab(self.resgraph, " Graphs and Charts ") self.pathz = QWidget() self.vlayout3 = QVBoxLayout(self.pathz) self.patz = QGroupBox(self.pathz) self.hboxlayout3 = QVBoxLayout(self.patz) self.profilepath = QLineEdit(profilerPath) self.getprofile = QPushButton(QIcon.fromTheme("document-open"), 'Open') self.getprofile.setToolTip( 'Dont touch if you dont know what are doing') self.getprofile.clicked.connect(lambda: self.profilepath.setText( str( QFileDialog.getOpenFileName( self.patz, ' Open the profile.py file ', path.expanduser("~"), ';;(profile.py)')))) self.hboxlayout3.addWidget( QLabel( '<center><b>Profile.py Python Library Full Path:</b></center>') ) self.hboxlayout3.addWidget(self.profilepath) self.hboxlayout3.addWidget(self.getprofile) self.argGroupBox = QGroupBox(self.pathz) self.hbxlayout = QHBoxLayout(self.argGroupBox) self.argLineEdit = QLineEdit(self.argGroupBox) self.argLineEdit.setToolTip( 'Not touch if you dont know what are doing') self.argLineEdit.setPlaceholderText( 'Dont touch if you dont know what are doing') self.hbxlayout.addWidget( QLabel('<b>Additional Profile Arguments:</b>')) self.hbxlayout.addWidget(self.argLineEdit) self.hboxlayout3.addWidget(self.argGroupBox) self.vlayout3.addWidget(self.patz) self.tabWidget.addTab(self.pathz, " Paths and Configs ") self.outp = QWidget() self.vlayout4 = QVBoxLayout(self.outp) self.outgro = QGroupBox(self.outp) self.outgro.setTitle(" MultiProcessing Output Logs ") self.hboxlayout4 = QVBoxLayout(self.outgro) self.outputlog = QTextEdit() self.outputlog.setText(''' I do not fear computers, I fear the lack of them. -Isaac Asimov ''') self.hboxlayout4.addWidget(self.outputlog) self.vlayout4.addWidget(self.outgro) self.tabWidget.addTab(self.outp, " Logs ") self.actionNew_profiling = QAction(QIcon.fromTheme("document-new"), 'New Profiling', self) self.actionLoad_profile = QAction(QIcon.fromTheme("document-open"), 'Open Profiling', self) self.actionClean = QAction(QIcon.fromTheme("edit-clear"), 'Clean', self) self.actionClean.triggered.connect(lambda: self.clearContent) self.actionAbout = QAction(QIcon.fromTheme("help-about"), 'About', self) self.actionAbout.triggered.connect(lambda: QMessageBox.about( self.dock, __doc__, ', '.join( (__doc__, __license__, __author__, __email__)))) self.actionSave_profile = QAction(QIcon.fromTheme("document-save"), 'Save Profiling', self) self.actionManual = QAction(QIcon.fromTheme("help-contents"), 'Help', self) self.actionManual.triggered.connect(lambda: open_new_tab( 'http://docs.python.org/library/profile.html')) self.tabWidget.setCurrentIndex(2) self.globalStatGroupBox.setTitle("Global Statistics") item = self.tableWidget.horizontalHeaderItem(0) item.setText("Number of Calls") item = self.tableWidget.horizontalHeaderItem(1) item.setText("Total Time") item = self.tableWidget.horizontalHeaderItem(2) item.setText("Per Call") item = self.tableWidget.horizontalHeaderItem(3) item.setText("Cumulative Time") item = self.tableWidget.horizontalHeaderItem(4) item.setText("Per Call") item = self.tableWidget.horizontalHeaderItem(5) item.setText("Filename") item = self.tableWidget.horizontalHeaderItem(6) item.setText("Line") item = self.tableWidget.horizontalHeaderItem(7) item.setText("Function") self.tabWidget.setTabText(self.tabWidget.indexOf(self.stat), "Statistics per Function") self.sourceTreeWidget.headerItem().setText(0, "Source files") self.tabWidget.setTabText(self.tabWidget.indexOf(self.source), "Sources Navigator") ####################################################################### self.scrollable, self.dock = QScrollArea(), QDockWidget() self.scrollable.setWidgetResizable(True) self.scrollable.setWidget(self.tabWidget) self.dock.setWindowTitle(__doc__) self.dock.setStyleSheet('QDockWidget::title{text-align: center;}') self.dock.setWidget(self.scrollable) QToolBar(self.dock).addActions( (self.actionNew_profiling, self.actionClean, self.actionSave_profile, self.actionLoad_profile, self.actionManual, self.actionAbout)) self.actionNew_profiling.triggered.connect( self.on_actionNew_profiling_triggered) self.actionLoad_profile.triggered.connect( self.on_actionLoad_profile_triggered) self.actionSave_profile.triggered.connect( self.on_actionSave_profile_triggered) self.locator.get_service('misc').add_widget( self.dock, QIcon.fromTheme("document-open-recent"), __doc__) if QSCI: # Scintilla source editor management self.scintillaWarningLabel.setText(' QScintilla is Ready ! ') layout = self.source.layout() layout.removeWidget(self.sourceTextEdit) self.sourceTextEdit = Qsci.QsciScintilla(self.source) layout.addWidget(self.sourceTextEdit, 0, 1) doc = self.sourceTextEdit doc.setLexer(Qsci.QsciLexerPython(self.sourceTextEdit)) doc.setReadOnly(True) doc.setEdgeMode(Qsci.QsciScintilla.EdgeLine) doc.setEdgeColumn(80) doc.setEdgeColor(QColor("#FF0000")) doc.setFolding(Qsci.QsciScintilla.BoxedTreeFoldStyle) doc.setBraceMatching(Qsci.QsciScintilla.SloppyBraceMatch) doc.setCaretLineVisible(True) doc.setMarginLineNumbers(1, True) doc.setMarginWidth(1, 25) doc.setTabWidth(4) doc.setEolMode(Qsci.QsciScintilla.EolUnix) self.marker = {} for color in COLORS: mnr = doc.markerDefine(Qsci.QsciScintilla.Background) doc.setMarkerBackgroundColor(color, mnr) self.marker[color] = mnr self.currentSourcePath = None # Connect table and tree filter edit signal to unique slot self.filterTableLineEdit.textEdited.connect( self.on_filterLineEdit_textEdited) # Timer to display filter hint message self.filterHintTimer = QTimer(self) self.filterHintTimer.setSingleShot(True) self.filterHintTimer.timeout.connect(self.on_filterHintTimer_timeout) # Timer to start search self.filterSearchTimer = QTimer(self) self.filterSearchTimer.setSingleShot(True) self.filterSearchTimer.timeout.connect( self.on_filterSearchTimer_timeout) self.tabLoaded = {} for i in range(10): self.tabLoaded[i] = False self.backgroundTreeMatchedItems = {} self.resizeWidgetToContent(self.tableWidget) def on_actionNew_profiling_triggered(self): self.clearContent() self.scriptPath = str( QFileDialog.getOpenFileName(self.dock, "Choose your script to profile", path.expanduser("~"), "Python (*.py *.pyw)")) commandLine = [ self.profilerPath, "-o", self.tempPath, self.scriptPath ] + self.scriptArgs commandLine = " ".join(commandLine) ##if self.termCheckBox.checkState() == Qt.Checked: #termList = ["xterm", "aterm"] #for term in termList: #termPath = which(term) #if termPath: #break #commandLine = """%s -e "%s ; echo 'Press ENTER Exit' ; read" """ \ #% (termPath, commandLine) self.process.start(commandLine) if not self.process.waitForStarted(): print((" ERROR: {} failed!".format(commandLine))) return def on_process_finished(self, exitStatus): ' whan the process end ' print((" INFO: OK: QProcess is %s" % self.process.exitCode())) self.output = self.process.readAll().data() if not self.output: self.output = " ERROR: FAIL: No output ! " self.outputlog.setText(self.output + str(self.process.exitCode())) if path.exists(self.tempPath): self.setStat(self.tempPath) remove(self.tempPath) else: self.outputlog.setText(" ERROR: QProcess FAIL: Profiling failed.") self.tabWidget.setCurrentIndex(2) def on_process_error(self, error): ' when the process fail, I hope you never see this ' print(" ERROR: QProcess FAIL: Profiler Dead, wheres your God now ? ") if error == QProcess.FailedToStart: self.outputlog.setText(" ERROR: FAIL: Profiler execution failed ") elif error == QProcess.Crashed: self.outputlog.setText(" ERROR: FAIL: Profiler execution crashed ") else: self.outputlog.setText(" ERROR: FAIL: Profiler unknown error ") def on_actionLoad_profile_triggered(self): """Load a previous profile sessions""" statPath = str( QFileDialog.getOpenFileName(self.dock, "Open profile dump", path.expanduser("~"), "Profile file (*)")) if statPath: self.clearContent() print(' INFO: OK: Loading profiling from ' + statPath) self.setStat(statPath) def on_actionSave_profile_triggered(self): """Save a profile sessions""" statPath = str( QFileDialog.getSaveFileName(self.dock, "Save profile dump", path.expanduser("~"), "Profile file (*)")) if statPath: #TODO: handle error case and give feelback to user print(' INFO: OK: Saving profiling to ' + statPath) self.stat.save(statPath) #=======================================================================# # Common parts # #=======================================================================# def on_tabWidget_currentChanged(self, index): """slot for tab change""" # Kill search and hint timer if running to avoid cross effect for timer in (self.filterHintTimer, self.filterSearchTimer): if timer.isActive(): timer.stop() if not self.stat: #No stat loaded, nothing to do return self.populateTable() self.populateSource() def on_filterLineEdit_textEdited(self, text): """slot for filter change (table or tree""" if self.filterSearchTimer.isActive(): # Already runnning, stop it self.filterSearchTimer.stop() # Start timer self.filterSearchTimer.start(300) def on_filterHintTimer_timeout(self): """Timeout to warn user about text length""" print("timeout") tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: label = self.filterHintTableLabel label.setText("Type > 2 characters to search") def on_filterSearchTimer_timeout(self): """timeout to start search""" tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: text = self.filterTableLineEdit.text() label = self.filterHintTableLabel edit = self.filterTableLineEdit widget = self.tableWidget else: print("Unknow tab for filterSearch timeout !") print(("do search for %s" % text)) if not len(text): # Empty keyword, just clean all if self.filterHintTimer.isActive(): self.filterHintTimer.stop() label.setText(" ? ") self.warnUSer(True, edit) self.clearSearch() return if len(text) < 2: # Don't filter if text is too short and tell it to user self.filterHintTimer.start(600) return else: if self.filterHintTimer.isActive(): self.filterHintTimer.stop() label.setText(" ? ") # Search self.clearSearch() matchedItems = [] if tab == TAB_FUNCTIONSTAT: # Find items matchedItems = widget.findItems(text, Qt.MatchContains) widget.setSortingEnabled(False) matchedRows = [item.row() for item in matchedItems] # Hide matched items header = widget.verticalHeader() for row in range(widget.rowCount()): if row not in matchedRows: header.hideSection(row) widget.setSortingEnabled(True) else: print(" Unknow tab for filterSearch timeout ! ") print(("got %s members" % len(matchedItems))) self.warnUSer(matchedItems, edit) self.resizeWidgetToContent(widget) def resizeWidgetToContent(self, widget): """Resize all columns according to content""" for i in range(widget.columnCount()): widget.resizeColumnToContents(i) def clearSearch(self): """Clean search result For table, show all items For tree, remove colored items""" tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: header = self.tableWidget.verticalHeader() if header.hiddenSectionCount(): for i in range(header.count()): if header.isSectionHidden(i): header.showSection(i) def clearContent(self): # Clear tabs self.tableWidget.clearContents() self.sourceTreeWidget.clear() # Reset LCD numbers for lcdNumber in (self.totalTimeLcdNumber, self.numCallLcdNumber, self.primCallLcdNumber): lcdNumber.display(1000000) # Reset stat self.pstat = None # Disable save as menu self.actionSave_profile.setEnabled(False) # Mark all tabs as unloaded for i in range(10): self.tabLoaded[i] = False def warnUSer(self, result, inputWidget): palette = inputWidget.palette() if result: palette.setColor(QPalette.Normal, QPalette.Base, QColor(255, 255, 255)) else: palette.setColor(QPalette.Normal, QPalette.Base, QColor(255, 136, 138)) inputWidget.setPalette(palette) inputWidget.update() def setStat(self, statPath): self.stat = Stat(path=statPath) # Global stat update self.totalTimeLcdNumber.display(self.stat.getTotalTime()) self.numCallLcdNumber.display(self.stat.getCallNumber()) self.primCallLcdNumber.display(self.stat.getPrimitiveCallRatio()) # Refresh current tab self.on_tabWidget_currentChanged(self.tabWidget.currentIndex()) # Activate save as menu self.actionSave_profile.setEnabled(True) try: self.rating.setMaxRating(10) self.rating.setRating( int(self.stat.getPrimitiveCallRatio()) / 10 - 1) except: pass #========================================================================# # Statistics table # #=======================================================================# def populateTable(self): row = 0 rowCount = self.stat.getStatNumber() progress = QProgressDialog("Populating statistics table...", "Abort", 0, 2 * rowCount) self.tableWidget.setSortingEnabled(False) self.tableWidget.setRowCount(rowCount) progress.setWindowModality(Qt.WindowModal) for (key, value) in self.stat.getStatItems(): #ncalls item = StatTableWidgetItem(str(value[0])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_NCALLS, item) colorTableItem(item, self.stat.getCallNumber(), value[0]) #total time item = StatTableWidgetItem(str(value[2])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_TTIME, item) colorTableItem(item, self.stat.getTotalTime(), value[2]) #per call (total time) if value[0] != 0: tPerCall = str(value[2] / value[0]) cPerCall = str(value[3] / value[0]) else: tPerCall = "" cPerCall = "" item = StatTableWidgetItem(tPerCall) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_TPERCALL, item) colorTableItem( item, 100.0 * self.stat.getTotalTime() / self.stat.getCallNumber(), tPerCall) #per call (cumulative time) item = StatTableWidgetItem(cPerCall) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_CPERCALL, item) colorTableItem( item, 100.0 * self.stat.getTotalTime() / self.stat.getCallNumber(), cPerCall) #cumulative time item = StatTableWidgetItem(str(value[3])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_CTIME, item) colorTableItem(item, self.stat.getTotalTime(), value[3]) #Filename self.tableWidget.setItem(row, STAT_FILENAME, StatTableWidgetItem(str(key[0]))) #Line item = StatTableWidgetItem(str(key[1])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_LINE, item) #Function name self.tableWidget.setItem(row, STAT_FUNCTION, StatTableWidgetItem(str(key[2]))) row += 1 # Store it in stat hash array self.stat.setStatLink(item, key, TAB_FUNCTIONSTAT) progress.setValue(row) if progress.wasCanceled(): return for i in range(self.tableWidget.rowCount()): progress.setValue(row + i) for j in range(self.tableWidget.columnCount()): item = self.tableWidget.item(i, j) if item: item.setFlags(Qt.ItemIsEnabled) self.tableWidget.setSortingEnabled(True) self.resizeWidgetToContent(self.tableWidget) progress.setValue(2 * rowCount) def on_tableWidget_itemDoubleClicked(self, item): matchedItems = [] filename = str(self.tableWidget.item(item.row(), STAT_FILENAME).text()) if not filename or filename.startswith("<"): # No source code associated, return immediatly return function = self.tableWidget.item(item.row(), STAT_FUNCTION).text() line = self.tableWidget.item(item.row(), STAT_LINE).text() self.on_tabWidget_currentChanged(TAB_SOURCE) # load source tab function = "%s (%s)" % (function, line) fathers = self.sourceTreeWidget.findItems(filename, Qt.MatchContains, SOURCE_FILENAME) print(("find %s father" % len(fathers))) for father in fathers: findItems(father, function, SOURCE_FILENAME, matchedItems) print(("find %s items" % len(matchedItems))) if matchedItems: self.tabWidget.setCurrentIndex(TAB_SOURCE) self.sourceTreeWidget.scrollToItem(matchedItems[0]) self.on_sourceTreeWidget_itemClicked(matchedItems[0], SOURCE_FILENAME) matchedItems[0].setSelected(True) else: print("oups, item found but cannot scroll to it !") #=======================================================================# # Source explorer # #=====================================================================# def populateSource(self): items = {} for stat in self.stat.getStatKeys(): source = stat[0] function = "%s (%s)" % (stat[2], stat[1]) if source in ("", "profile") or source.startswith("<"): continue # Create the function child child = QTreeWidgetItem([function]) # Store it in stat hash array self.stat.setStatLink(child, stat, TAB_SOURCE) if source in items: father = items[source] else: # Create the father father = QTreeWidgetItem([source]) items[source] = father father.addChild(child) self.sourceTreeWidget.setSortingEnabled(False) for value in list(items.values()): self.sourceTreeWidget.addTopLevelItem(value) self.sourceTreeWidget.setSortingEnabled(True) def on_sourceTreeWidget_itemActivated(self, item, column): self.on_sourceTreeWidget_itemClicked(item, column) def on_sourceTreeWidget_itemClicked(self, item, column): line = 0 parent = item.parent() if QSCI: doc = self.sourceTextEdit if parent: pathz = parent.text(column) result = match("(.*) \(([0-9]+)\)", item.text(column)) if result: try: function = str(result.group(1)) line = int(result.group(2)) except ValueError: # We got garbage... falling back to line 0 pass else: pathz = item.text(column) pathz = path.abspath(str(pathz)) if self.currentSourcePath != pathz: # Need to load source self.currentSourcePath == pathz try: if QSCI: doc.clear() doc.insert(file(pathz).read()) else: self.sourceTextEdit.setPlainText(file(pathz).read()) except IOError: QMessageBox.warning(self, "Error", "Source file could not be found", QMessageBox.Ok) return if QSCI: for function, line in [(i[2], i[1]) for i in self.stat.getStatKeys() if i[0] == pathz]: # expr, regexp, case sensitive, whole word, wrap, forward doc.findFirst("def", False, True, True, False, True, line, 0, True) end, foo = doc.getCursorPosition() time = self.stat.getStatTotalTime((pathz, line, function)) colorSource(doc, self.stat.getTotalTime(), time, line, end, self.marker) if QSCI: doc.ensureLineVisible(line)
class GraphDialog(QDialog): edit_patterns = 0 edit_curves = 1 titles = {edit_patterns: 'Pattern editor', edit_curves: 'Curve editor'} labels = {edit_patterns: 'Patterns', edit_curves: 'Curves'} def __init__(self, dockwidget, parent, params, edit_type): QDialog.__init__(self, parent) main_lay = QVBoxLayout(self) self.dockwidget = dockwidget self.params = params self.edit_type = edit_type self.x_label = '' self.y_label = '' self.setMinimumWidth(600) self.setMinimumHeight(400) self.setWindowTitle(self.titles[edit_type]) # TODO: softcode self.setWindowModality(QtCore.Qt.ApplicationModal) self.current = None self.current_saved = False # File self.lbl_file = QLabel('File:') self.fra_file = QFrame() self.fra_file.setContentsMargins(0, 0, 0, 0) fra_file_lay = QHBoxLayout(self.fra_file) if edit_type == self.edit_patterns: self.txt_file = QLineEdit(self.params.patterns_file) elif edit_type == self.edit_curves: self.txt_file = QLineEdit(self.params.curves_file) self.txt_file.setReadOnly(True) self.txt_file.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self.txt_file.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) fra_file_lay.addWidget(self.txt_file) self.btn_file = QPushButton('Change') # TODO: softcode self.btn_file.clicked.connect(self.import_file) fra_file_lay.addWidget(self.btn_file) fra_file_lay.setContentsMargins(0, 0, 0, 0) self.lbl_list = QLabel(self.labels[edit_type]) self.lst_list = QListWidget() self.lst_list.currentItemChanged.connect(self.list_item_changed) # Form self.fra_form = QFrame() fra_form1_lay = QFormLayout(self.fra_form) fra_form1_lay.setContentsMargins(0, 0, 0, 0) fra_form1_lay.addRow(self.lbl_list, self.lst_list) # Buttons self.fra_buttons = QFrame() fra_buttons_lay = QHBoxLayout(self.fra_buttons) fra_buttons_lay.setContentsMargins(0, 0, 0, 0) if self.edit_type == self.edit_patterns: ele_name = 'pattern' elif self.edit_type == self.edit_curves: ele_name = 'curve' self.btn_new = QPushButton('New ' + ele_name) # TODO: softcode self.btn_new.clicked.connect(self.new_element) fra_buttons_lay.addWidget(self.btn_new) self.btn_import = QPushButton('Import ' + ele_name + 's') # TODO: softcode self.btn_import.clicked.connect(self.import_file) fra_buttons_lay.addWidget(self.btn_import) self.btn_save = QPushButton('Save current ' + ele_name) # TODO: softcode self.btn_save.clicked.connect(self.save) fra_buttons_lay.addWidget(self.btn_save) self.btn_del = QPushButton('Delete current ' + ele_name) # TODO: softcode self.btn_del.clicked.connect(self.del_item) fra_buttons_lay.addWidget(self.btn_del) # ID self.lbl_id = QLabel('ID:') self.txt_id = QLineEdit() self.txt_id.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.MinimumExpanding) self.lbl_desc = QLabel('Desc.:') self.txt_desc = QLineEdit() self.fra_id = QFrame() fra_id_lay = QHBoxLayout(self.fra_id) fra_id_lay.addWidget(self.lbl_id) fra_id_lay.addWidget(self.txt_id) fra_id_lay.addWidget(self.lbl_desc) fra_id_lay.addWidget(self.txt_desc) # Table form self.table = QTableWidget(self) self.rows_nr = 24 self.cols_nr = 2 self.table.setRowCount(self.rows_nr) self.table.setColumnCount(self.cols_nr) self.table.verticalHeader().setVisible(False) # Initialize empty table self.clear_table() self.table.itemChanged.connect(self.data_changed) self.fra_table = QFrame() fra_table_lay = QVBoxLayout(self.fra_table) fra_table_lay.setContentsMargins(0, 0, 0, 0) if edit_type == self.edit_curves: self.fra_pump_type = QFrame() fra_pump_type_lay = QFormLayout(self.fra_pump_type) self.lbl_pump_type = QLabel('Curve type:') # TODO: softcode self.cbo_pump_type = QComboBox() for key, name in Curve.type_names.iteritems(): self.cbo_pump_type.addItem(name, key) fra_pump_type_lay.addRow(self.lbl_pump_type, self.cbo_pump_type) fra_table_lay.addWidget(self.fra_pump_type) self.cbo_pump_type.activated.connect(self.cbo_pump_type_activated) fra_table_lay.addWidget(self.table) self.btn_add_row = QPushButton('Add row') self.btn_add_row.clicked.connect(self.add_row) fra_table_lay.addWidget(self.btn_add_row) # Graph canvas self.fra_graph = QFrame() self.static_canvas = StaticMplCanvas(self.fra_graph, width=5, height=4, dpi=100) fra_graph_lay = QVBoxLayout(self.fra_graph) fra_graph_lay.addWidget(self.static_canvas) # Top frame self.fra_top = QFrame() fra_top_lay = QVBoxLayout(self.fra_top) fra_top_lay.addWidget(self.fra_form) fra_top_lay.addWidget(self.fra_id) fra_top_lay.addWidget(self.fra_buttons) # Bottom frame self.fra_bottom = QFrame() fra_bottom_lay = QHBoxLayout(self.fra_bottom) fra_bottom_lay.addWidget(self.fra_table) fra_bottom_lay.addWidget(self.fra_graph) # Main main_lay.addWidget(self.fra_top) main_lay.addWidget(self.fra_bottom) # Get existing patterns/curves self.need_to_update_graph = False if self.edit_type == self.edit_patterns: for pattern_id, pattern in self.params.patterns.iteritems(): self.lst_list.addItem(pattern.id) elif self.edit_type == self.edit_curves: for curve_id, curve in self.params.curves.iteritems(): self.lst_list.addItem(curve.id) if self.lst_list.count() > 0: self.lst_list.setCurrentRow(0) self.txt_id.setEnabled(True) self.txt_desc.setEnabled(True) self.btn_save.setEnabled(True) self.btn_del.setEnabled(True) self.table.setEnabled(True) self.table.setEditTriggers(QAbstractItemView.AllEditTriggers) else: self.txt_id.setEnabled(False) self.txt_desc.setEnabled(False) self.btn_save.setEnabled(False) self.btn_del.setEnabled(False) self.table.setEnabled(False) self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.new_dialog = None self.need_to_update_graph = True def cbo_pump_type_activated(self): self.update_table_headers() self.update_graph() def add_row(self): row_pos = self.table.rowCount() self.table.insertRow(row_pos) col = 0 item = QTableWidgetItem(str(row_pos)) if self.edit_type == self.edit_patterns: self.table.setItem(row_pos, col, item) item.setFlags(QtCore.Qt.ItemIsSelectable) def setVisible(self, bool): QDialog.setVisible(self, bool) self.update_table_headers() self.update_graph() def list_item_changed(self): p_index = self.lst_list.currentRow() flags = Qt.ItemFlags() flags != Qt.ItemIsEnabled # Clear table self.clear_table() self.need_to_update_graph = False if p_index >= 0: self.table.setRowCount(0) if self.edit_type == self.edit_patterns: self.current = self.params.patterns[ self.lst_list.currentItem().text()] for v in range(len(self.current.values)): row_pos = self.table.rowCount() self.table.insertRow(row_pos) item = QTableWidgetItem(str(v)) item.setFlags(flags) self.table.setItem(v, 0, item) self.table.setItem( v, 1, QTableWidgetItem(str(self.current.values[v]))) elif self.edit_type == self.edit_curves: self.current = self.params.curves[ self.lst_list.currentItem().text()] for v in range(len(self.current.xs)): row_pos = self.table.rowCount() self.table.insertRow(row_pos) self.table.setItem( v, 0, QTableWidgetItem(str(self.current.xs[v]))) self.table.setItem( v, 1, QTableWidgetItem(str(self.current.ys[v]))) curve_type = self.current.type self.cbo_pump_type.setCurrentIndex(curve_type) # Update GUI self.txt_id.setText(self.current.id) self.txt_desc.setText(self.current.desc) self.update_table_headers() # Update graph self.need_to_update_graph = True self.update_graph() else: # No curves self.txt_id.setText('') self.txt_desc.setText('') # Update table and chart self.need_to_update_graph = False for v in range(self.table.columnCount()): self.table.setItem(v, 1, QTableWidgetItem('')) self.need_to_update_graph = True self.update_graph() def import_file(self): config_file = ConfigFile(Parameters.config_file_path) directory = None if self.edit_type == GraphDialog.edit_curves: directory = self.params.last_curves_dir elif self.edit_type == GraphDialog.edit_patterns: directory = self.params.last_patterns_dir if directory is None: directory = self.params.last_project_dir file_path = QFileDialog.getOpenFileName(self, 'Select file', directory, 'Files (*.txt *.inp)') if file_path is None or file_path == '': return else: if self.edit_type == GraphDialog.edit_patterns: # Save patterns file path in configuration file config_file.set_patterns_file_path(file_path) Parameters.patterns_file = file_path elif self.edit_type == GraphDialog.edit_curves: # Save curve file path in configuration file config_file.set_curves_file_path(file_path) Parameters.curves_file = file_path self.read(file_path) def read(self, file_path): self.lst_list.clear() if self.edit_type == self.edit_patterns: InpFile.read_patterns(self.params, file_path) for pattern_id, pattern in self.params.patterns.iteritems(): # desc = ' (' + pattern.desc + ')' if pattern.desc is not None else '' self.lst_list.addItem(pattern.id) self.params.patterns[pattern.id] = pattern elif self.edit_type == self.edit_curves: InpFile.read_curves(self.params, file_path) for curve_id, curve in self.params.curves.iteritems(): # desc = ' (' + curve.desc + ')' if curve.desc is not None else '' self.lst_list.addItem(curve.id) self.params.curves[curve.id] = curve if self.lst_list.count() > 0: self.lst_list.setCurrentRow(0) def new_element(self): old_ids = [] if self.edit_type == self.edit_patterns: for pattern in self.params.patterns.itervalues(): old_ids.append(pattern.id) elif self.edit_type == self.edit_curves: for curve in self.params.curves.itervalues(): old_ids.append(curve.id) self.new_dialog = NewIdDialog(self, old_ids) self.new_dialog.exec_() new_id = self.new_dialog.get_newid() description = self.new_dialog.get_description() if new_id is None or description is None: return if self.edit_type == self.edit_patterns: new_pattern = Pattern(new_id, description) self.params.patterns[new_pattern.id] = new_pattern self.lst_list.addItem(new_pattern.id) elif self.edit_type == self.edit_curves: curve_type = self.cbo_pump_type.itemData( self.cbo_pump_type.currentIndex()) new_curve = Curve(new_id, curve_type, desc=description) self.params.curves[new_curve.id] = new_curve self.lst_list.addItem(new_curve.id) self.lst_list.setCurrentRow(self.lst_list.count() - 1) self.txt_id.setText(new_id) self.txt_desc.setText(description) # Clear table self.clear_table() self.static_canvas.axes.clear() self.txt_id.setEnabled(True) self.txt_desc.setEnabled(True) self.btn_save.setEnabled(True) self.btn_del.setEnabled(True) self.table.setEnabled(True) self.table.setEditTriggers(QAbstractItemView.AllEditTriggers) def save(self): self.need_to_update_graph = False # Check for ID if not self.txt_id.text(): QMessageBox.warning( self, Parameters.plug_in_name, u'Please specify the ID.', # TODO: softcode QMessageBox.Ok) return if self.edit_type == GraphDialog.edit_patterns: values = [] for row in range(self.table.rowCount()): item = self.table.item(row, 1) if item is not None and item.text() != '': values.append(self.from_item_to_val(item)) else: values.append('0') pattern = Pattern(self.txt_id.text(), self.txt_desc.text(), values) old_patterns = self.params.patterns old_patterns[pattern.id] = pattern self.params.patterns = old_patterns self.lst_list.currentItem().setText(pattern.id) elif self.edit_type == GraphDialog.edit_curves: # Check for ID unique xs = [] ys = [] for row in range(self.table.rowCount()): item_x = self.table.item(row, 0) item_y = self.table.item(row, 1) if item_x.text() != '' and item_y.text() != '': xs.append(self.from_item_to_val(item_x)) ys.append(self.from_item_to_val(item_y)) curve_type = self.cbo_pump_type.itemData( self.cbo_pump_type.currentIndex()) curve = Curve(self.txt_id.text(), curve_type, self.txt_desc.text()) for v in range(len(xs)): curve.append_xy(xs[v], ys[v]) old_curves = self.params.curves old_curves[curve.id] = curve self.params.curves = old_curves self.lst_list.currentItem().setText(curve.id) # Update GUI self.dockwidget.update_curves_combo() # self.read() self.need_to_update_graph = True def clear_table(self): self.need_to_update_graph = False for r in range(self.table.rowCount()): self.table.setItem(r, 0, QTableWidgetItem(None)) self.table.setItem(r, 1, QTableWidgetItem(None)) for row in range(self.rows_nr): for col in range(self.cols_nr): if self.edit_type == self.edit_patterns: if col == 0: item = QTableWidgetItem(str(row)) self.table.setItem(row, col, item) item.setFlags(QtCore.Qt.ItemIsSelectable) # elif col == 1 and row == 0: # item = QTableWidgetItem(str(1)) # self.table.setItem(row, col, item) # item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) # elif self.edit_type == self.edit_curves: # if row == 0: # item = QTableWidgetItem(str(0)) # self.table.setItem(row, 0, item) # item = QTableWidgetItem(str(1)) # self.table.setItem(row, 1, item) # item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.need_to_update_graph = True def del_item(self): selected_row = self.lst_list.currentRow() name = self.lst_list.currentItem().text() if selected_row < 0: return self.lst_list.takeItem(selected_row) if self.lst_list.count() == 0: self.txt_id.setEnabled(False) self.txt_desc.setEnabled(False) self.btn_save.setEnabled(False) self.btn_del.setEnabled(False) self.table.setEnabled(False) self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) if self.edit_type == GraphDialog.edit_curves: del self.params.curves[name] # Update GUI self.dockwidget.update_curves_combo() elif self.edit_type == GraphDialog.edit_patterns: del self.params.patterns[name] # Update GUI self.dockwidget.update_patterns_combo() def data_changed(self): if self.need_to_update_graph: self.update_graph() def update_table_headers(self): if self.edit_type == self.edit_patterns: self.x_label = 'Time period' self.y_label = 'Multiplier' elif self.edit_type == self.edit_curves: cbo_data = self.cbo_pump_type.itemData( self.cbo_pump_type.currentIndex()) if cbo_data == Curve.type_efficiency: self.x_label = 'Flow ' + '[' + self.params.options.flow_units + ']' self.y_label = 'Efficiency ' + '[' + self.params.options.units_deltaz[ self.params.options.units] + ']' if cbo_data == Curve.type_headloss: self.x_label = 'Flow ' + '[' + self.params.options.flow_units + ']' self.y_label = 'Headloss ' + '[' + self.params.options.units_deltaz[ self.params.options.units] + ']' if cbo_data == Curve.type_pump: self.x_label = 'Flow ' + '[' + self.params.options.flow_units + ']' self.y_label = 'Head ' + '[' + self.params.options.units_deltaz[ self.params.options.units] + ']' if cbo_data == Curve.type_volume: self.x_label = 'Height ' + '[' + self.params.options.flow_units + ']' self.y_label = 'Volume ' + '[' + self.params.options.units_deltaz[ self.params.options.units] + ']' self.table.setHorizontalHeaderLabels([self.x_label, self.y_label]) # TODO: softcode def update_graph(self): if not self.need_to_update_graph: return xs = [] ys = [] for row in range(self.table.rowCount()): item = self.table.item(row, 0) x = self.from_item_to_val(item) item = self.table.item(row, 1) y = self.from_item_to_val(item) if x is not None: xs.append(float(x)) if y is not None: ys.append(float(y)) if len(xs) == 0 or len(ys) == 0: self.static_canvas.clear() return xys_t = zip(xs, ys) xys_t.sort() xys = zip(*xys_t) xs = xys[0] ys = xys[1] if self.edit_type == self.edit_patterns: y_axis_label = 'Mult. avg.: ' + '{0:.2f}'.format( (numpy.average(ys))) self.static_canvas.draw_bars_graph( ys, time_period=self.params.times.pattern_timestep, y_axes_label=y_axis_label) elif self.edit_type == self.edit_curves: # Account for different types of curves cbo_data = self.cbo_pump_type.itemData( self.cbo_pump_type.currentIndex()) series_length = min(len(xs), len(ys)) # Need to account for different types of curves if cbo_data == Curve.type_efficiency or cbo_data == Curve.type_headloss or cbo_data == Curve.type_volume: self.static_canvas.draw_line_graph(xs[:series_length], ys[:series_length], self.x_label, self.y_label) elif cbo_data == Curve.type_pump: if series_length == 1 or series_length == 3: if series_length == 1: # 3 curve points curve_xs = [0, xs[0], xs[0] * 2] curve_ys = [ys[0] * 1.33, ys[0], 0] # y = a * x^2 + b * x + c elif series_length == 3: # 3 curve points curve_xs = [xs[0], xs[1], xs[2]] curve_ys = [ys[0], ys[1], ys[2]] (a, b, c) = numpy.polyfit(curve_xs, curve_ys, 2) # Create a few interpolated values interp_xs = [] interp_ys = [] n_vals = 30 for v in range(n_vals + 1): x = (curve_xs[2] - curve_xs[0]) / n_vals * v interp_xs.append(x) y = a * x**2 + b * x + c interp_ys.append(y) self.static_canvas.draw_line_graph(interp_xs, interp_ys, self.x_label, self.y_label) else: self.static_canvas.draw_line_graph(xs[:series_length], ys[:series_length], self.x_label, self.y_label) def from_item_to_val(self, item): if item is None: value = None else: value = item.text() try: value = float(value) value = max(value, 0) except: value = None return value
class DataItemWidget(QWidget): __EXTRA_COLUMN_WIDTH = 20 def __init__(self, me_cls, data): QWidget.__init__(self) self.__me_cls = me_cls self.__setup_ui() self.__data = data self.__init_data() self.__connect_slot() def __setup_ui(self): v_layout = QVBoxLayout() self.__tool_widget = ToolWidget() self.__tool_widget.setMaximumHeight(40) self.__data_table_widget = QTableWidget() self.__data_table_widget.horizontalHeader().setStretchLastSection(True) self.__data_table_widget.horizontalHeader().setResizeMode( 0, QHeaderView.Fixed) v_layout.setSpacing(0) v_layout.setContentsMargins(0, 0, 0, 0) v_layout.addWidget(self.__tool_widget, 0) v_layout.addWidget(self.__data_table_widget, 1) self.setLayout(v_layout) def __connect_slot(self): pass #self.connect(self.__tool_widget,SIGNAL('refresh_btn_clicked()'), self ,SLOT('__on_refresh_signal()')) def __init_data(self): self.__data_table_widget.clearContents() # init header #self.__colume_names = yt_connection.get_table_colume_names(self.__table_name) self.__colume_names = [x.field.name for x in self.__me_cls.attributes] #self.__colume_names.insert(0,'entity_id') #print self.__colume_names self.__data_table_widget.setColumnCount(len(self.__colume_names)) head_list = QStringList() for colume in self.__colume_names: head_list << colume self.__data_table_widget.setHorizontalHeaderLabels(head_list) # default the header column both sides are coverd, these codes add __EXTRA_COLUMN_WIDTH to the header column width # and reise column width in function self.__adjust_table_colume() self.__data_table_widget.horizontalHeader().setResizeMode( QHeaderView.ResizeToContents) self.__record_colume_header_width() self.__data_table_widget.horizontalHeader().setResizeMode( QHeaderView.Interactive) self.__record_colume_header_width() show_data = [] for entity_id, value in self.__data.items(): item = value.obj_data.copy() item['managed_entity_id'] = entity_id show_data.append(item) self.__update_table(show_data) # init data # data = yt_connection.get_table_data(self.__table_name) # # self.__update_table(data) self.__adjust_table_colume() def __record_colume_header_width(self): count = self.__data_table_widget.columnCount() self.__column_widths = [] for i in range(count): self.__column_widths.append( self.__data_table_widget.columnWidth(i) + self.__EXTRA_COLUMN_WIDTH) ''' data like this [ {u'direction': 'to-lport', u'name': '[]', u'priority': '100', u'log': 'true', u'action': 'drop', u'external_ids': '{"neutron:lport"="5fb77332-2035-4f72-8e57-7415b02489c9"}', u'match': '"outport==\\"inside-vm2\\""', u'severity': '[]', 'uuid': '2890a832-1c83-4b8e-8b40-2928817012cc'} ] ''' def __update_table(self, data): self.__data_table_widget.clearContents() row_num = 0 for row in data: self.__data_table_widget.insertRow(row_num) colume_num = 0 for colume in self.__colume_names: if row.has_key(colume): item_str = str(row[colume]) else: item_str = 'None' table_wid_item = QTableWidgetItem(item_str) table_wid_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.__data_table_widget.setItem(row_num, colume_num, table_wid_item) colume_num += 1 row_num += 1 def __clear_table_data(self): row_count = self.__data_table_widget.rowCount() rev = [i for i in range(row_count)] rev.reverse() for i in rev: self.__data_table_widget.removeRow(i) def __adjust_table_colume(self): self.__data_table_widget.resizeColumnsToContents() count = self.__data_table_widget.columnCount() for i in range(count): col_wid = self.__data_table_widget.columnWidth(i) if col_wid < self.__column_widths[i]: self.__data_table_widget.setColumnWidth( i, self.__column_widths[i])
class OWxsh_waviness(widget.OWWidget): name = "xsh_waviness" id = "orange.widgets.preprocessor.xsh_waviness" description = "xoppy application to compute..." icon = "icons/waviness.png" author = "Luca Rebuffi" maintainer_email = "[email protected]; [email protected]" priority = 10 category = "" keywords = ["xoppy", "xsh_waviness"] outputs = [{"name": "PreProcessor_Data", "type": ShadowPreProcessorData, "doc": "PreProcessor Data", "id": "PreProcessor_Data"}] want_main_area = 1 want_control_area = 1 WIDGET_WIDTH = 1100 WIDGET_HEIGHT = 650 xx = None yy = None zz = None number_of_points_x = Setting(10) number_of_points_y = Setting(100) dimension_x = Setting(20.1) dimension_y = Setting(113.1) estimated_slope_error = Setting(0.9) montecarlo_seed = Setting(2387427) waviness_file_name = Setting('waviness.dat') harmonic_maximum_index = Setting(60) data = Setting({'c': ['0.3', '0.1', '0.1', '0.0', '0.0', '0.0', '0.3', '0.0', '0.0', '0.3', '0.0', '0.0', '0.5', '0.0', '0.0', '0.2', '0.2', '0.2', '0.9', '0.0', '0.0', '0.0', '0.0', '0.0', '0.4', '0.0', '0.0', '0.4', '0.0', '0.0', '0.0', '0.6', '0.6', '0.0', '0.4', '0.4', '0.0', '0.4', '0.4', '0.1', '0.4', '0.4', '0.1', '0.2', '0.2', '0.0', '0.2', '0.2', '0.0', '0.3', '0.3', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0'], 'y': ['0.0', '-0.1', '-0.1', '0.0', '0.0', '0.0', '0.03', '0.0', '0.0', '0.2', '0.0', '0.0', '0.2', '0.0', '0.0', '0.1', '0.1', '0.1', '0.1', '0.0', '0.0', '0.0', '0.0', '0.0', '0.01', '0.0', '0.0', '0.03', '0.0', '0.0', '0.0', '0.02', '0.02', '0.0', '0.1', '0.1', '0.0', '0.1', '0.1', '0.0', '0.1', '0.1', '0.0', '0.0', '0.0', '0.0', '0.3', '0.3', '0.0', '0.2', '0.2', '0.0', '0.2', '0.2', '0.0', '0.2', '0.2', '0.0', '0.0', '0.0', '0.0'], 'g': ['0.0', '0.3', '0.3', '0.0', '0.0', '0.0', '0.05', '0.0', '0.0', '0.05', '0.0', '0.0', '0.1', '0.0', '0.0', '0.05', '0.05', '0.05', '0.2', '0.0', '0.0', '0.0', '0.0', '0.0', '0.1', '0.0', '0.0', '0.1', '0.0', '0.0', '0.0', '0.2', '0.2', '0.0', '0.1', '0.1', '0.0', '0.1', '0.1', '0.0', '0.1', '0.1', '0.0', '0.0', '0.0', '0.0', '0.1', '0.1', '0.0', '0.2', '0.2', '0.0', '0.1', '0.1', '0.0', '0.1', '0.1', '0.0', '0.0', '0.0', '0.0']}) def __init__(self): super().__init__() geom = QApplication.desktop().availableGeometry() self.setGeometry(QRect(round(geom.width() * 0.05), round(geom.height() * 0.05), round(min(geom.width() * 0.98, self.WIDGET_WIDTH)), round(min(geom.height() * 0.95, self.WIDGET_HEIGHT)))) gen_box = ShadowGui.widgetBox(self.controlArea, "Waviness Parameters", addSpace=True, orientation="horizontal", width=500) tabs_setting = gui.tabWidget(gen_box) tab_input = ShadowGui.createTabPage(tabs_setting, "Input Parameter") tab_harmonics = ShadowGui.createTabPage(tabs_setting, "Harmonics") tab_out = ShadowGui.createTabPage(tabs_setting, "Output") self.input_box = ShadowGui.widgetBox(tab_input, "Inputs", addSpace=True, orientation="vertical", width=470) gui.button(self.input_box, self, "Load xsh_waviness input file ...", callback=self.load_inp_file) gui.separator(self.input_box) ShadowGui.lineEdit(self.input_box, self, "number_of_points_x", "Number of Points (<201) X (width)", labelWidth=300, valueType=int, orientation="horizontal") ShadowGui.lineEdit(self.input_box, self, "number_of_points_y", " Y (length)", labelWidth=300, valueType=int, orientation="horizontal") gui.separator(self.input_box) ShadowGui.lineEdit(self.input_box, self, "dimension_x", "Dimensions [cm] X (width)", labelWidth=300, valueType=float, orientation="horizontal") ShadowGui.lineEdit(self.input_box, self, "dimension_y", " Y (length)", labelWidth=300, valueType=float, orientation="horizontal") gui.separator(self.input_box) ShadowGui.lineEdit(self.input_box, self, "estimated_slope_error", "Estimated slope error [arcsec]", labelWidth=300, valueType=float, orientation="horizontal") ShadowGui.lineEdit(self.input_box, self, "montecarlo_seed", "Monte Carlo initial seed", labelWidth=300, valueType=int, orientation="horizontal") self.output_box = ShadowGui.widgetBox(tab_input, "Outputs", addSpace=True, orientation="vertical", width=470) self.select_file_box = ShadowGui.widgetBox(self.output_box, "", addSpace=True, orientation="horizontal") gui.separator(self.output_box) gui.button(self.output_box, self, "Write xsh_waviness input file (optional) ...", callback=self.write_inp_file) ShadowGui.lineEdit(self.select_file_box, self, "waviness_file_name", "Output File Name", labelWidth=120, valueType=str, orientation="horizontal") self.harmonics_box = ShadowGui.widgetBox(tab_harmonics, "Harmonics", addSpace=True, orientation="vertical", width=470, height=690) ShadowGui.lineEdit(self.harmonics_box, self, "harmonic_maximum_index", "Harmonic Maximum Index", labelWidth=300, valueType=int, orientation="horizontal", callback=self.set_harmonics) gui.separator(self.harmonics_box) self.scrollarea = QScrollArea() self.scrollarea.setMaximumWidth(400) self.harmonics_box.layout().addWidget(self.scrollarea, alignment=Qt.AlignHCenter) self.shadow_output = QTextEdit() self.shadow_output.setReadOnly(True) out_box = ShadowGui.widgetBox(tab_out, "System Output", addSpace=True, orientation="horizontal", height=600) out_box.layout().addWidget(self.shadow_output) button_box = ShadowGui.widgetBox(self.controlArea, "", addSpace=False, orientation="horizontal") button = gui.button(button_box, self, "Calculate Waviness", callback=self.calculate_waviness) button.setFixedHeight(45) button.setFixedWidth(170) button = gui.button(button_box, self, "Generate Waviness File", callback=self.generate_waviness_file) font = QFont(button.font()) font.setBold(True) button.setFont(font) palette = QPalette(button.palette()) # make a copy of the palette palette.setColor(QPalette.ButtonText, QColor('Dark Blue')) button.setPalette(palette) # assign new palette button.setFixedHeight(45) button.setFixedWidth(200) button = gui.button(button_box, self, "Reset Fields", callback=self.call_reset_settings) font = QFont(button.font()) font.setItalic(True) button.setFont(font) palette = QPalette(button.palette()) # make a copy of the palette palette.setColor(QPalette.ButtonText, QColor('Dark Red')) button.setPalette(palette) # assign new palette button.setFixedHeight(45) button.setFixedWidth(120) gui.rubber(self.controlArea) self.figure = Figure(figsize=(600, 600)) self.figure.patch.set_facecolor('white') self.axis = self.figure.add_subplot(111, projection='3d') self.axis.set_xlabel("X (cm)") self.axis.set_ylabel("Y (cm)") self.axis.set_zlabel("Z (µm)") self.figure_canvas = FigureCanvasQTAgg(self.figure) self.mainArea.layout().addWidget(self.figure_canvas) gui.rubber(self.mainArea) def restoreWidgetPosition(self): super().restoreWidgetPosition() self.table = QTableWidget(self.harmonic_maximum_index + 1, 3) self.table.setAlternatingRowColors(True) self.table.horizontalHeader().setResizeMode(QHeaderView.Fixed) for i in range(0, 3): self.table.setColumnWidth(i, 70) horHeaders = [] verHeaders = [] for n, key in enumerate(sorted(self.data.keys())): horHeaders.append(key) for m, item in enumerate(self.data[key]): table_item = QTableWidgetItem(str(item)) table_item.setTextAlignment(Qt.AlignRight) self.table.setItem(m, n, table_item) verHeaders.append(str(m)) self.table.setHorizontalHeaderLabels(horHeaders) self.table.setVerticalHeaderLabels(verHeaders) self.table.resizeRowsToContents() self.table.itemChanged.connect(self.table_item_changed) self.scrollarea.setWidget(self.table) self.scrollarea.setWidgetResizable(1) gui.rubber(self.controlArea) def reload_harmonics_table(self): horHeaders = [] verHeaders = [] self.table.itemChanged.disconnect(self.table_item_changed) self.table.clear() row_count = self.table.rowCount() for n in range(0, row_count): self.table.removeRow(0) for index in range(0, self.harmonic_maximum_index + 1): self.table.insertRow(0) for n, key in enumerate(sorted(self.data.keys())): horHeaders.append(key) for m, item in enumerate(self.data[key]): table_item = QTableWidgetItem(str(item)) table_item.setTextAlignment(Qt.AlignRight) self.table.setItem(m, n, table_item) verHeaders.append(str(m)) self.table.setHorizontalHeaderLabels(horHeaders) self.table.setVerticalHeaderLabels(verHeaders) self.table.resizeRowsToContents() for i in range(0, 3): self.table.setColumnWidth(i, 70) self.table.itemChanged.connect(self.table_item_changed) def table_item_changed(self): dict = {} message = "" error_row_index = -1 error_column_index = -1 previous_value = "" try: row_count = self.harmonic_maximum_index + 1 for column_index in range(0, self.table.columnCount()): column_name = self.table.horizontalHeaderItem(column_index).data(0) row_content = [] for row_index in range(0, row_count): if not self.table.item(row_index, column_index) is None: message = "Value at row " + str( row_index) + " and column \'" + column_name + "\' is not numeric" error_row_index = row_index error_column_index = column_index previous_value = self.data[column_name][row_index] value = float(self.table.item(row_index, column_index).data(0)) # to raise exception row_content.append(str(value)) dict[column_name] = row_content self.data = dict except ValueError: QMessageBox.critical(self, "QMessageBox.critical()", message + "\nValue is reset to previous value", QMessageBox.Ok) table_item = QTableWidgetItem(previous_value) table_item.setTextAlignment(Qt.AlignRight) self.table.setItem(error_row_index, error_column_index, table_item) self.table.setCurrentCell(error_row_index, error_column_index) except Exception as exception: QMessageBox.critical(self, "QMessageBox.critical()", exception.args[0], QMessageBox.Ok) def set_harmonics(self): if self.harmonic_maximum_index < 0: QMessageBox.critical(self, "QMessageBox.critical()", "Harmonic Maximum Index should be a positive integer number", QMessageBox.Ok) else: row_count = len(self.data["c"]) if self.harmonic_maximum_index + 1 > row_count: for n, key in enumerate(sorted(self.data.keys())): for m in range(row_count, self.harmonic_maximum_index + 1): self.data[key].append('0.0') else: for n, key in enumerate(sorted(self.data.keys())): self.data[key] = copy.deepcopy(self.data[key][0: self.harmonic_maximum_index + 1]) self.reload_harmonics_table() def load_inp_file(self): file_name = QFileDialog.getOpenFileName(self, "Select a input file for XSH_WAVINESS", ".", "*.inp") if not file_name is None: sys.stdout = EmittingStream(textWritten=self.writeStdOut) if not file_name.strip() == "": dict = ST.waviness_read(file=file_name) self.number_of_points_x = dict["npointx"] self.number_of_points_y = dict["npointy"] self.dimension_y = dict["xlength"] self.dimension_x = dict["width"] self.estimated_slope_error = dict["slp"] self.montecarlo_seed = dict["iseed"] self.waviness_file_name = dict["file"].strip('\n\r').strip() self.harmonic_maximum_index = dict["nharmonics"] self.data["c"] = self.to_str_array(dict["c"]) self.data["y"] = self.to_str_array(dict["y"]) self.data["g"] = self.to_str_array(dict["g"]) self.reload_harmonics_table() def write_inp_file(self): try: sys.stdout = EmittingStream(textWritten=self.writeStdOut) self.check_fields() file_name = self.waviness_file_name.strip().split(sep=".dat")[0] + ".inp" dict = {} dict["npointx"] = self.number_of_points_x dict["npointy"] = self.number_of_points_y dict["xlength"] = self.dimension_y dict["width"] = self.dimension_x dict["slp"] = self.estimated_slope_error dict["iseed"] = self.montecarlo_seed dict["file"] = self.waviness_file_name.strip('\n\r') dict["nharmonics"] = self.harmonic_maximum_index dict["c"] = self.to_float_array(self.data["c"]) dict["y"] = self.to_float_array(self.data["y"]) dict["g"] = self.to_float_array(self.data["g"]) ST.waviness_write(dict, file=file_name) QMessageBox.information(self, "QMessageBox.information()", "File \'" + file_name + "\' written to disk", QMessageBox.Ok) except Exception as exception: QMessageBox.critical(self, "QMessageBox.critical()", exception.args[0], QMessageBox.Ok) def calculate_waviness(self): try: sys.stdout = EmittingStream(textWritten=self.writeStdOut) self.check_fields() xx, yy, zz = ST.waviness_calc(npointx=self.number_of_points_x, npointy=self.number_of_points_y, width=self.dimension_x, xlength=self.dimension_y, slp=self.estimated_slope_error, nharmonics=self.harmonic_maximum_index, iseed=self.montecarlo_seed, c=self.to_float_array(self.data["c"]), y=self.to_float_array(self.data["y"]), g=self.to_float_array(self.data["g"])) self.xx = xx self.yy = yy self.zz = zz self.axis.clear() x_to_plot, y_to_plot = numpy.meshgrid(xx, yy) z_to_plot = [] for y_index in range(0, len(yy)): z_array = [] for x_index in range(0, len(xx)): z_array.append(1e4 * float(zz[x_index][y_index])) # to micron z_to_plot.append(z_array) z_to_plot = numpy.array(z_to_plot) self.axis.plot_surface(x_to_plot, y_to_plot, z_to_plot, rstride=1, cstride=1, cmap=cm.autumn, linewidth=0.5, antialiased=True) slope, sloperms = ST.slopes(zz, xx, yy) title = ' Slope error rms in X direction: %f arcsec' % (sloperms[0]) + '\n' + \ ' : %f urad' % (sloperms[2]) + '\n' + \ ' Slope error rms in Y direction: %f arcsec' % (sloperms[1]) + '\n' + \ ' : %f urad' % (sloperms[3]) self.axis.set_xlabel("X (cm)") self.axis.set_ylabel("Y (cm)") self.axis.set_zlabel("Z (µm)") self.axis.set_title(title) self.axis.mouse_init() self.figure_canvas.draw() QMessageBox.information(self, "QMessageBox.information()", "Waviness calculated: if the result is satisfactory,\nclick \'Generate Waviness File\' to complete the operation ", QMessageBox.Ok) except Exception as exception: QMessageBox.critical(self, "QMessageBox.critical()", exception.args[0], QMessageBox.Ok) def generate_waviness_file(self): if not self.zz is None and not self.yy is None and not self.xx is None: if not self.waviness_file_name is None: self.waviness_file_name = self.waviness_file_name.strip() if self.waviness_file_name == "": raise Exception("Output File Name missing") else: raise Exception("Output File Name missing") sys.stdout = EmittingStream(textWritten=self.writeStdOut) ST.write_shadow_surface(self.zz.T, self.xx, self.yy, outFile=self.waviness_file_name) QMessageBox.information(self, "QMessageBox.information()", "Waviness file " + self.waviness_file_name + " written on disk", QMessageBox.Ok) self.send("PreProcessor_Data", ShadowPreProcessorData(waviness_data_file=self.waviness_file_name)) def call_reset_settings(self): if ConfirmDialog.confirmed(parent=self, message="Confirm Reset of the Fields?"): try: self.resetSettings() self.reload_harmonics_table() except: pass def check_fields(self): self.number_of_points_x = ShadowGui.checkStrictlyPositiveNumber(self.number_of_points_x, "Number of Points X") self.number_of_points_y = ShadowGui.checkStrictlyPositiveNumber(self.number_of_points_y, "Number of Points Y") self.dimension_x = ShadowGui.checkStrictlyPositiveNumber(self.dimension_x, "Dimension X") self.dimension_y = ShadowGui.checkStrictlyPositiveNumber(self.dimension_y, "Dimension Y") self.estimated_slope_error = ShadowGui.checkPositiveNumber(self.estimated_slope_error, "Estimated slope error") self.montecarlo_seed = ShadowGui.checkPositiveNumber(self.montecarlo_seed, "Monte Carlo initial seed") self.harmonic_maximum_index = ShadowGui.checkPositiveNumber(self.harmonic_maximum_index, "Harmonic Maximum Index") if not self.waviness_file_name is None: self.waviness_file_name = self.waviness_file_name.strip() if self.waviness_file_name == "": raise Exception("Output File Name missing") else: raise Exception("Output File Name missing") def to_float_array(self, string_array): float_array = [] for index in range(len(string_array)): float_array.append(float(string_array[index])) return float_array def to_str_array(self, float_array): string_array = [] for index in range(len(float_array)): string_array.append(str(float_array[index])) return string_array def writeStdOut(self, text): cursor = self.shadow_output.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertText(text) self.shadow_output.setTextCursor(cursor) self.shadow_output.ensureCursorVisible()
class multirun_widget(QWidget): """Widget for editing multirun values. Keyword arguments: tr -- a translate instance that contains the experimental sequence nrows -- number of rows = number of multirun steps. ncols -- number of columns = number of channels to change in one step. order -- the order to produce the variables list in: ascending - with repeats next to each other descending - with repeats next to each other random - completely randomise the order coarse random - randomise order but repeats next to each other unsorted - make an ascending list, then repeat the list """ multirun_vals = pyqtSignal(np.ndarray) # the array of multirun values progress = pyqtSignal(str) # string detailing the progress of the multirun def __init__(self, tr, nrows=1, ncols=1, order='ascending'): super().__init__() self.tr = tr # translator for the current sequence self.mrtr = tr.copy() # translator for multirun sequence self.msglist = [] # list of multirun sequences as XML string self.ind = 0 # index for how far through the multirun we are self.nrows = nrows self.ncols = ncols self.types = OrderedDict([('measure', int), ('measure_prefix', str), ('1st hist ID', int), ('Variable label', str), ('Order', str), ('Type', strlist), ('Analogue type', strlist), ('Time step name', listlist), ('Analogue channel', listlist), ('runs included', listlist), ('Last time step run', str), ('Last time step end', str), ('# omitted', int), ('# in hist', int), ('list index', strlist)]) self.ui_param = OrderedDict([ ('measure', 0), ('measure_prefix', 'Measure0'), ('1st hist ID', -1), ('Variable label', ''), ('Order', order), ('Type', ['Time step length'] * ncols), ('Analogue type', ['Fast analogue'] * ncols), ('Time step name', [[]] * ncols), ('Analogue channel', [[]] * ncols), ('runs included', [[] for i in range(nrows)]), ('Last time step run', r'C:\Users\lab\Desktop\DExTer 1.4\Last Timesteps\feb2020_940and812.evt' ), ('Last time step end', r'C:\Users\lab\Desktop\DExTer 1.4\Last Timesteps\feb2020_940and812.evt' ), ('# omitted', 5), ('# in hist', 100), ('list index', ['0'] * ncols) ]) self.awg_args = [ 'duration_[ms]', 'off_time_[us]', 'freqs_input_[MHz]', 'start_freq_[MHz]', 'end_freq_[MHz]', 'hybridicity', 'num_of_traps', 'distance_[um]', 'tot_amp_[mV]', 'dc_offset_[mV]', 'start_amp', 'end_amp', 'start_output_[Hz]', 'end_output_[Hz]', 'freq_amp', 'mod_freq_[kHz]', 'mod_depth', 'freq_phase_[deg]', 'freq_adjust', 'amp_adjust', 'freqs_output_[Hz]', 'num_of_samples', 'duration_loop_[ms]', 'number_of_cycles' ] self.dds_args = [ 'Freq', 'Phase', 'Amp', 'Start_add', 'End_add', 'Step_rate', 'Sweep_start', 'Sweep_end', 'Pos_step', 'Neg_step', 'Pos_step_rate', 'Neg_step_rate' ] self.slm_args = [ 'f', 'period', 'angle', 'radius', 'gradient', 'shift', 'radial', 'azimuthal', 'amplitude' ] self.column_options = [ 'Analogue voltage', 'AWG1 chan : seg', 'AWG2 chan : seg', 'DDS1 port : profile', 'DDS2 module : profile', 'SLM holograms' ] # these analogue types require the analogue options self.col_range_text = [''] * ncols self.COM = ['RB1A', 'RB2', 'RB3', 'RB4', 'RB1B'] # DDS COM port connections self.COM2 = ['1557', '977', '1013', '420'] # DDS2 module connections self.mr_param = copy.deepcopy( self.ui_param) # parameters used for current multirun self.mr_vals = [] # multirun values for the current multirun self.mr_queue = [ ] # list of parameters, sequences, and values to queue up for future multiruns self.appending = False # whether the current multirun will be appended on to the displayed results self.multirun = False # whether a multirun is running or not self.QueueWindow = QMainWindow() # window for editing mr queue self.QueueWindow.setStyleSheet("background-color: cyan;") self.queue_ui = Ui_QueueWindow(self.mr_queue) self.queue_ui.setupUi(self.QueueWindow) self.init_UI() # make the widgets self.ss = sequenceSaver(self.mrtr, self.mr_vals, self.mr_param, '') # used to save sequences def make_label_edit(self, label_text, layout, position=[0, 0, 1, 1], default_text='', validator=None): """Make a QLabel with an accompanying QLineEdit and add them to the given layout with an input validator. The position argument should be [row number, column number, row width, column width].""" label = QLabel(label_text, self) layout.addWidget(label, *position) line_edit = QLineEdit(self) if np.size(position) == 4: position[1] += 1 layout.addWidget(line_edit, *position) line_edit.setText(default_text) line_edit.setValidator(validator) return label, line_edit def init_UI(self): """Create all of the widget objects required""" layout = QVBoxLayout() self.setLayout(layout) # place scroll bars if the contents of the window are too large scroll = QScrollArea(self) layout.addWidget(scroll) scroll_content = QWidget(scroll) scroll.setWidgetResizable(True) scroll.setFixedHeight(800) self.grid = QGridLayout() scroll_content.setLayout(self.grid) #### validators for user input #### double_validator = QDoubleValidator() # floats int_validator = QIntValidator(0, 10000000) # positive integers msr_validator = QIntValidator(-1, 1000000) # integers >= -1 nat_validator = QIntValidator(1, 10000000) # natural numbers col_validator = QIntValidator(1, self.ncols - 1) # for number of columns #### table dimensions and ordering #### # choose the number of rows = number of multirun steps labels = ['# Omit', '# in Histogram', '# Columns', '# Rows'] default = ['5', '100', str(self.ncols), str(self.nrows)] vldtr = [int_validator, nat_validator, nat_validator, nat_validator] self.omit_edit, self.nhist_edit, self.cols_edit, self.rows_edit = [ self.make_label_edit(labels[i], self.grid, [0, 2 * i, 1, 1], default[i], vldtr[i])[1] for i in range(4) ] self.cols_edit.textChanged[str].connect(self.change_array_size) self.rows_edit.textChanged[str].connect(self.change_array_size) self.omit_edit.editingFinished.connect(self.update_repeats) self.nhist_edit.editingFinished.connect(self.update_repeats) # choose the order self.order_edit = QComboBox(self) self.order_edit.addItems( ['ascending', 'descending', 'random', 'coarse random', 'unsorted']) self.grid.addWidget(self.order_edit, 0, 8, 1, 1) #### create multirun list of values #### # metadata for the multirun list: which channels and timesteps self.measures = OrderedDict() labels = ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] defaults = ['Variable 0', '0', 'Measure0', '0'] for i in range(len(labels)): label = QLabel(labels[i], self) self.grid.addWidget(label, i + 1, 0, 1, 1) self.measures[labels[i]] = QLineEdit(defaults[i], self) self.measures[labels[i]].textChanged.connect(self.update_all_stats) self.grid.addWidget(self.measures[labels[i]], i + 1, 1, 1, 3) self.measures['measure'].setValidator(int_validator) self.measures['1st hist ID'].setValidator(msr_validator) label.setText('1st ID (-1 to append)') # change label self.chan_choices = OrderedDict() labels = [ 'Type', 'Time step name', 'Analogue type', 'Analogue channel' ] sht = self.tr.get_esc()[2][2:] # 'Sequence header top' options = [ [ 'Time step length', 'Analogue voltage', 'GPIB', 'AWG1 chan : seg', 'AWG2 chan : seg', 'DDS1 port : profile', 'DDS2 module : profile', 'SLM holograms', 'Other' ], list( map(str.__add__, [str(i) for i in range(len(sht))], [': ' + hc[6][1].text for hc in sht])), # time step names ['Fast analogue', 'Slow analogue'], self.get_anlg_chans('Fast') ] positions = [[1, 4, 3, 2], [1, 6, 6, 1], [1, 7, 3, 1], [1, 8, 6, 1]] widgets = [QComboBox, QListWidget] for i in range(0, len(labels)): self.chan_choices[labels[i]] = widgets[i % 2]() if i % 2: self.chan_choices[labels[i]].setSelectionMode(3) self.chan_choices[labels[i]].addItems(options[i]) self.grid.addWidget(self.chan_choices[labels[i]], *positions[i]) self.chan_choices['Type'].currentTextChanged[str].connect( self.change_mr_type) self.chan_choices['Analogue type'].currentTextChanged[str].connect( self.change_mr_anlg_type) self.chan_choices['Analogue channel'].setEnabled(False) # enter desired time step selection via python cmd self.index_slice = QLineEdit('range(0,1,2)', self) self.grid.addWidget(self.index_slice, 3, 4, 3, 2) self.apply_slice_btn = QPushButton('Apply range', self) self.grid.addWidget(self.apply_slice_btn, 4, 4, 3, 2) self.apply_slice_btn.clicked.connect(self.apply_slice) # AWG takes a list for some arguments, so needs an index label = QLabel('List index:', self) self.grid.addWidget(label, 3, 7, 3, 1) self.list_index = QLineEdit('0', self) self.grid.addWidget(self.list_index, 4, 7, 3, 1) self.list_index.setValidator(int_validator) self.list_index.textEdited[str].connect(self.save_chan_selection) # add a new list of multirun values to the array self.col_index = self.make_label_edit('column index:', self.grid, position=[5, 0, 1, 1], default_text='0', validator=col_validator)[1] self.col_range = QLineEdit('np.linspace(0,1,%s)' % (self.nrows), self) self.grid.addWidget(self.col_range, 5, 2, 1, 2) # show the previously selected channels for this column: self.chan_choices['Time step name'].itemClicked.connect( self.save_chan_selection) self.chan_choices['Analogue channel'].itemClicked.connect( self.save_chan_selection) self.col_range.editingFinished.connect(self.save_chan_selection) self.col_index.textChanged[str].connect(self.set_chan_listbox) # add the column to the multirun values array add_var_button = QPushButton('Add column', self) add_var_button.clicked.connect(self.add_column_to_array) add_var_button.resize(add_var_button.sizeHint()) self.grid.addWidget(add_var_button, 6, 0, 1, 1) # clear the current list of user variables clear_vars_button = QPushButton('Clear', self) clear_vars_button.clicked.connect(self.clear_array) clear_vars_button.resize(clear_vars_button.sizeHint()) self.grid.addWidget(clear_vars_button, 6, 1, 1, 1) # suggest new measure when multirun started self.suggest_button = QPushButton('Auto-increment measure', self, checkable=True, checked=True) self.suggest_button.resize(self.suggest_button.sizeHint()) self.grid.addWidget(self.suggest_button, 6, 2, 1, 2) # choose last time step for multirun lts_label = QLabel('Last time step: ', self) self.grid.addWidget(lts_label, 7, 0, 1, 1) self.last_step_run_edit = self.make_label_edit('Running: ', self.grid, position=[7, 1, 1, 3])[1] self.last_step_run_edit.setText(self.ui_param['Last time step run']) self.last_step_run_edit.textChanged[str].connect(self.update_last_step) self.last_step_end_edit = self.make_label_edit('End: ', self.grid, position=[7, 5, 1, 3])[1] self.last_step_end_edit.setText(self.ui_param['Last time step end']) self.last_step_end_edit.textChanged[str].connect(self.update_last_step) # display current progress multirun_progress = QLabel( 'User variable: , omit 0 of 0 files, 0 of 100 histogram files, 0% complete' ) self.grid.addWidget(multirun_progress, 8, 0, 1, 12) reset_slot(self.progress, multirun_progress.setText, True) # table stores multirun values: self.table = QTableWidget(self.nrows, self.ncols) self.reset_array() self.grid.addWidget(self.table, 9, 0, 20, 12) scroll.setWidget(scroll_content) #### #### array editing functions #### #### def reset_array(self, newvals=None): """Empty the table of its values. If newvals are supplied then it should have the right shape (rows, cols) so that it can be used to fill the table items.""" self.table.setHorizontalHeaderLabels(list(map(str, range(self.ncols)))) if not newvals: newvals = [[''] * self.ncols] * self.nrows for i in range(self.table.rowCount()): for j in range(self.ncols): self.table.setItem(i, j, QTableWidgetItem()) self.table.item(i, j).setText(newvals[i][j]) def clear_array(self): """Empty the table of its values and reset the selected channels.""" self.reset_array() self.ui_param['Type'] = ['Time step length'] * self.ncols self.ui_param['Analogue type'] = ['Fast analogue'] * self.ncols self.ui_param['Time step name'] = [[]] * self.ncols self.ui_param['Analogue channel'] = [[]] * self.ncols self.ui_param['list index'] = ['0'] * self.ncols self.col_range_text = self.col_range_text[:self.ncols] + [''] * ( self.ncols - len(self.col_range_text)) self.set_chan_listbox(0) def check_table(self): """Check that there are values in each of the cells of the array.""" try: for i in range(self.table.rowCount()): for j in range(self.table.columnCount()): _ = float(self.table.item(i, j).text()) return 1 except ValueError: return 0 def get_table(self): """Return a list of all the values in the multirun array table""" return [[ self.table.item(i, j).text() for j in range(self.table.columnCount()) ] for i in range(self.table.rowCount())] def change_array_size(self): """Update the size of the multirun array based on the number of rows and columns specified in the line edit.""" self.nrows = int(self.rows_edit.text()) if self.rows_edit.text() else 1 if self.nrows < 1: self.nrows = 1 self.table.setRowCount(self.nrows) self.ncols = int(self.cols_edit.text()) if self.cols_edit.text() else 1 if self.ncols < 1: self.ncols = 1 self.table.setColumnCount(self.ncols) self.col_index.setValidator(QIntValidator(1, self.ncols - 1)) if self.col_index.text() and int( self.col_index.text()) > self.ncols - 1: self.col_index.setText(str(self.ncols - 1)) self.reset_array() self.col_range_text = self.col_range_text[:self.ncols] + [''] * ( self.ncols - len(self.col_range_text)) self.ui_param['runs included'] = [[] for i in range(self.nrows)] for key, default in zip([ 'Type', 'Analogue type', 'Time step name', 'Analogue channel', 'list index' ], ['Time step length', 'Fast analogue', [], [], '0']): for i in range(len(self.ui_param[key]), self.ncols): self.ui_param[key].append(default) if len(self.ui_param[key]) > self.ncols: self.ui_param[key] = self.ui_param[key][:self.ncols] def update_all_stats(self, toggle=False): """Shorthand to update the values of the stats dictionary from the text labels.""" self.update_repeats() self.update_last_step() for key in self.measures.keys( ): # ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] if self.measures[key].text( ): # don't do anything if the line edit is empty try: self.ui_param[key] = self.types[key]( self.measures[key].text()) except: pass # probably while user was typing the '-' in '-1' def update_repeats(self, txt=''): """Take the current values of the line edits and use them to set the number of omitted and number of included runs in a histogram.""" self.ui_param['# omitted'] = int( self.omit_edit.text()) if self.omit_edit.text() else 0 self.ui_param['# in hist'] = int( self.nhist_edit.text()) if self.nhist_edit.text() else 1 def update_last_step(self, txt=''): """Save the current values of the last time step file paths.""" self.ui_param['Last time step run'] = self.last_step_run_edit.text() self.ui_param['Last time step end'] = self.last_step_end_edit.text() def apply_slice(self): """Use the text in the index slice line edit to select time steps""" try: self.chan_choices['Time step name'].clearSelection() for i in eval(self.index_slice.text()): try: self.chan_choices['Time step name'].item(i).setSelected( True) except AttributeError: pass # index out of range self.save_chan_selection() except (TypeError, ValueError, NameError) as e: warning('Invalid selection command for multirun timesteps "' + self.index_slice.text() + '".\n' + str(e)) def add_column_to_array(self): """Make a list of values and add it to the given column in the multirun values array. The function is chosen by the user. Values are repeated a set number of times, ordered according to the ComboBox text. The selected channels are stored in lists.""" try: # make the list of values table = np.array(self.get_table()).T c = [column.astype(float) for column in table if '' not in column] vals = eval(self.col_range.text()) except Exception as e: warning('Add column to multirun: invalid syntax "' + self.col_range.text() + '".\n' + str(e)) return 0 col = int(self.col_index.text()) if self.col_index.text() else 0 # store the selected channels self.ui_param['Order'] = self.order_edit.currentText() for key in self.measures.keys( ): # ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] if self.measures[key].text( ): # don't do anything if the line edit is empty self.ui_param[key] = self.types[key](self.measures[key].text()) # order the list of values if self.ui_param['Order'] == 'descending': vals = list(reversed(vals)) elif 'random' in self.ui_param['Order']: vals = list(vals) shuffle(vals) for i in range(self.table.rowCount()): try: # set vals in table cells self.table.item(i, col).setText('%.4f' % vals[i]) except IndexError: # occurs if invalid range self.table.item(i, col).setText('') #### multirun channel selection #### def reset_sequence(self, tr): """Update the translator object used to get the experimental sequence. This is used to set the labels for time step names and channel names. Note: the multirun sequence mrtr is not affected.""" self.tr = tr self.change_mr_type(self.chan_choices['Type'].currentText()) # note: selected channels might have changed order self.set_chan_listbox(self.col_index.text()) def save_chan_selection(self, arg=None): """When the user changes the selection of channels/timesteps for the given column, save it. The selection will be reloaded if the user changes the column and then comes back.""" try: if self.col_index.text(): col = int(self.col_index.text()) for key in ['Type', 'Analogue type']: self.ui_param[key][col] = self.chan_choices[ key].currentText() for key in ['Time step name', 'Analogue channel']: self.ui_param[key][col] = list( map(self.chan_choices[key].row, self.chan_choices[key].selectedItems())) self.ui_param['list index'][col] = int( self.list_index.text()) if self.list_index.text() else 0 self.col_range_text[col] = self.col_range.text() except (ValueError, IndexError) as e: error("Multirun couldn't save channel choices for column " + self.col_index.text() + '.\n' + str(e)) def set_chan_listbox(self, col): """Set the selected channels and timesteps with the values previously stored for the given column col. If there were no values stored previously or the index is out of range, reset the selection.""" try: col = int(col) if col else 0 mrtype = self.ui_param['Type'][col] antype = self.ui_param['Analogue type'][col] sel = { 'Time step name': self.ui_param['Time step name'][col], 'Analogue channel': self.ui_param['Analogue channel'][col] if any( mrtype == x for x in self.column_options) else [] } list_ind = self.ui_param['list index'][col] col_range_txt = self.col_range_text[col] except (IndexError, ValueError): mrtype, antype = 'Time step length', 'Fast analogue' sel = {'Time step name': [], 'Analogue channel': []} list_ind = 0 col_range_txt = '' self.col_range.setText(col_range_txt) self.list_index.setText(str(list_ind)) self.chan_choices['Type'].setCurrentText(mrtype) self.chan_choices['Analogue type'].setCurrentText(antype) self.chan_choices['Analogue channel'].setEnabled( any(mrtype == x for x in self.column_options)) for key in ['Time step name', 'Analogue channel']: self.chan_choices[key].setCurrentRow( 0, QItemSelectionModel.Clear) # clear previous selection try: for i in sel[key]: # select items at the stored indices self.chan_choices[key].item(i).setSelected(True) except IndexError: pass # perhaps sequence was updated but using old selection indices except AttributeError as e: warning( "Couldn't set channels for the loaded multirun parameters. Load the sequence first, then load multirun parameters.\n" + str(e)) def setListboxFlag(self, listbox, flag): """Set the items of the listbox all have the given flag. e.g. self.setListboxFlag(self.chan_choices['Time step name'], ~Qt.ItemIsEditable)""" for i in range(listbox.count()): item = listbox.item(i) item.setFlags(item.flags() | flag) def get_anlg_chans(self, speed): """Return a list of name labels for the analogue channels. speed -- 'Fast' or 'Slow'""" chans = self.tr.get_esc()[5 if speed == 'Fast' else 10][2:] return [c[2][1].text + ': ' + c[3][1].text for c in chans] def change_mr_type(self, newtype): """Enable/Disable list boxes to reflect the multirun type: newtype[str] -- Time step length: only needs timesteps -- Analogue voltage: also needs channels -- AWG: takes float values but with a list index.""" sht = self.tr.get_esc()[2][2:] # 'Sequence header top' if newtype == 'AWG1 chan : seg' or newtype == 'AWG2 chan : seg': self.chan_choices['Time step name'].clear() self.chan_choices['Time step name'].addItems( [str(i) + ', ' + str(j) for j in range(100) for i in range(2)]) reset_slot( self.chan_choices['Analogue type'].currentTextChanged[str], self.change_mr_anlg_type, False) self.chan_choices['Analogue type'].clear() self.chan_choices['Analogue type'].addItems(['AWG Parameter']) self.chan_choices['Analogue channel'].setEnabled(True) self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems(self.awg_args) elif 'DDS' in newtype: self.chan_choices['Time step name'].clear() if 'DDS2' in newtype: ddsoptions = [ '%s : P%s - ' % (i + 1, j) + self.COM2[i] for i in range(4) for j in range(8) ] for i in range(4): ddsoptions.insert(i * 9 + 8, '%s : aux - ' % (i + 1) + self.COM2[i]) else: ddsoptions = [ 'COM%s : P%s - ' % (i + 7, j) + self.COM[i] for i in range(5) for j in range(8) ] for i in range(5): ddsoptions.insert(i * 9 + 8, 'COM%s : aux - ' % (i + 7) + self.COM[i]) self.chan_choices['Time step name'].addItems(ddsoptions) reset_slot( self.chan_choices['Analogue type'].currentTextChanged[str], self.change_mr_anlg_type, False) self.chan_choices['Analogue type'].clear() self.chan_choices['Analogue type'].addItems(['DDS Parameter']) self.chan_choices['Analogue channel'].setEnabled(True) self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems(self.dds_args) elif newtype == 'SLM holograms': self.chan_choices['Time step name'].clear() slmoptions = ['Hologram %s' % (i) for i in range(9)] self.chan_choices['Time step name'].addItems(slmoptions) reset_slot( self.chan_choices['Analogue type'].currentTextChanged[str], self.change_mr_anlg_type, False) self.chan_choices['Analogue type'].clear() self.chan_choices['Analogue type'].addItems(['Hologram Parameter']) self.chan_choices['Analogue channel'].setEnabled(True) self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems(self.slm_args) else: if any(self.chan_choices['Analogue type'].currentText() == x for x in ['AWG Parameter', 'DDS Parameter', 'Hologram Parameter']): self.chan_choices['Analogue type'].clear() self.chan_choices['Analogue type'].addItems( ['Fast analogue', 'Slow analogue']) self.chan_choices['Analogue type'].currentTextChanged[ str].connect(self.change_mr_anlg_type) if newtype == 'Other': self.chan_choices['Analogue channel'].setEnabled(False) self.chan_choices['Time step name'].clear() self.chan_choices['Time step name'].addItems(['Variable']) elif newtype == 'Time step length': self.chan_choices['Analogue channel'].setEnabled(False) self.chan_choices['Time step name'].clear() self.chan_choices['Time step name'].addItems( list( map(str.__add__, [str(i) for i in range(len(sht))], [': ' + hc[6][1].text for hc in sht]))) # time step names elif newtype == 'Analogue voltage': self.chan_choices['Time step name'].clear() self.chan_choices['Time step name'].addItems( list( map(str.__add__, [str(i) for i in range(len(sht))], [': ' + hc[6][1].text for hc in sht]))) # time step names self.chan_choices['Analogue channel'].setEnabled(True) self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems( self.get_anlg_chans( self.chan_choices['Analogue type'].currentText().split( ' ')[0])) def change_mr_anlg_type(self, newtype): """Change the analogue channels listbox when fast/slow analogue channels are selected.""" if self.chan_choices['Analogue channel'].isEnabled(): self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems( self.get_anlg_chans( self.chan_choices['Analogue type'].currentText().split( ' ')[0])) def get_next_index(self, rn): """Choose the next index from the rows of the table to use in the multirun, based on the order chosen. rn: the ID of the current run within the multirun. make rn modulo nrows so that there isn't an index error on the last run.""" # if self.mr_param['Order'] == 'unsorted': # return rn % self.nrows # elif self.mr_param['Order'] == 'random': # return randint(0, self.nrows - 1) # else: # if descending, ascending, or coarse random, the order has already been set return (rn // (self.mr_param['# omitted'] + self.mr_param['# in hist']) ) % len(self.mr_param['runs included'] ) # ID of histogram in repetition cycle def get_next_sequence(self, i=None): """Use the values in the multirun array to make the next sequence to run in the multirun. Uses saved mr_param not UI""" if i == None: i = self.ind # row index esc = self.mrtr.get_esc() # shorthand num_s = len(esc[2]) - 2 # number of steps try: for col in range(len(self.mr_vals[i])): # edit the sequence try: val = float(self.mr_vals[i][col]) if self.mr_param['Type'][col] == 'Time step length': for head in [2, 9]: for t in self.mr_param['Time step name'][col]: esc[head][t + 2][3][1].text = str(val) elif self.mr_param['Type'][col] == 'Analogue voltage': for t in self.mr_param['Time step name'][col]: for c in self.mr_param['Analogue channel'][col]: if 'Fast' in self.mr_param['Analogue type'][ col]: esc[6][t + c * num_s + 3][3][1].text = str(val) else: esc[11][t + c * num_s + 3][3][1].text = str(val) except ValueError as e: pass # non-float variable self.mrtr.set_routine_name('Multirun ' + self.mr_param['Variable label'] + \ ': ' + self.mr_vals[i][0] + ' (%s / %s)'%(i+1, len(self.mr_vals))) except IndexError as e: error('Multirun failed to edit sequence at ' + self.mr_param['Variable label'] + ' = ' + self.mr_vals[i][0] + '\n' + str(e)) return self.mrtr.write_to_str() def get_all_sequences(self, save_dir=''): """Use the multirun array vals to make all of the sequences that will be used in the multirun, then store these as a list of XML strings.""" self.msglist = [] for i in range(len(self.mr_vals)): self.msglist.append(self.get_next_sequence(i)) if not self.ss.isRunning(): self.ss = sequenceSaver(self.mrtr, self.mr_vals, self.mr_param, save_dir) self.ss.start(self.ss.LowestPriority) # save the sequences else: # a backup if the first is busy saving sequences self.s2 = sequenceSaver(self.mrtr, self.mr_vals, self.mr_param, save_dir) self.s2.start(self.s2.LowestPriority) #### save and load parameters #### def view_mr_queue(self): """Show the window for editing the multirun queue""" self.queue_ui.updateList() self.QueueWindow.show() def try_browse(self, title='Select a File', file_type='all (*)', open_func=QFileDialog.getOpenFileName): """Open a file dialog and retrieve a file name from the browser. title: String to display at the top of the file browser window file_type: types of files that can be selected open_func: the function to use to open the file browser""" try: if 'PyQt4' in sys.modules: file_name = open_func(self, title, '', file_type) elif 'PyQt5' in sys.modules: file_name, _ = open_func(self, title, '', file_type) return file_name except OSError: return '' # probably user cancelled def save_mr_params(self, save_file_name=''): """Save the variable label, measure, measure prefix, # runs omitted, # runs per histogram, multirun type, list of timesteps, multirun # analogue type, list of channels, and array of variables.""" if not save_file_name: save_file_name = self.try_browse( title='Save File', file_type='csv(*.csv);;all (*)', open_func=QFileDialog.getSaveFileName) if save_file_name: if hasattr(self.sender(), 'text') and self.sender().text() == 'Save Parameters': params, vals = self.ui_param, self.get_table() # save from UI else: params, vals = self.mr_param, self.mr_vals # save from multirun try: with open(save_file_name, 'w+') as f: f.write('Multirun list of variables:\n') f.write(';'.join([ ','.join( [vals[row][col] for col in range(len(vals[0]))]) for row in range(len(vals)) ]) + '\n') f.write(';'.join(params.keys()) + '\n') f.write(';'.join(map(str, list(params.values())))) except (PermissionError, FileNotFoundError) as e: error("Couldn't save Multirun params to file: %s\n" % save_file_name + str(e)) def load_mr_params(self, load_file_name=''): """Load the multirun variables array from a file.""" if not load_file_name: load_file_name = self.try_browse( title='Load File', file_type='csv(*.csv);;all (*)', open_func=QFileDialog.getOpenFileName) if load_file_name: with open(load_file_name, 'r') as f: _ = f.readline() vals = [ x.split(',') for x in f.readline().replace('\n', '').split(';') ] header = f.readline().replace('\n', '').split(';') params = f.readline().split(';') for i in range(len(header)): if header[i] in self.ui_param: try: self.ui_param[header[i]] = self.types[header[i]]( params[i]) except ValueError as e: error( 'Multirun editor could not load parameter: %s\n' % params[i] + str(e)) # store values in case they're overwritten after setText() nrows, ncols = np.shape(vals) # update array of values col = int(self.col_index.text()) if self.col_index.text() else 0 nhist, nomit = map( str, [self.ui_param['# in hist'], self.ui_param['# omitted']]) runstep, endstep = self.ui_param[ 'Last time step run'], self.ui_param['Last time step end'] # then update the label edits for key in self.measures.keys( ): # update variable label and measure reset_slot(self.measures[key].textChanged, self.update_all_stats, False) self.measures[key].setText(str(self.ui_param[key])) reset_slot(self.measures[key].textChanged, self.update_all_stats, True) self.set_chan_listbox(col if col < ncols else 0) self.rows_edit.setText(str(nrows)) # triggers change_array_size self.cols_edit.setText(str(ncols)) self.change_array_size() # don't wait for it to be triggered self.reset_array(vals) self.nhist_edit.setText(nhist) self.omit_edit.setText(nomit) self.last_step_run_edit.setText( runstep) # triggers update_last_step self.last_step_end_edit.setText(endstep) for i in range( len(header) ): # restore values as change_array_size loads defaults if header[i] in self.ui_param: try: self.ui_param[header[i]] = self.types[header[i]]( params[i]) except ValueError as e: pass def check_mr_params(self, save_results_path='.'): """Check that the multirun parameters are valid before adding it to the queue""" if 'PyDex default empty sequence' in self.tr.get_routine_name(): QMessageBox.warning( self, 'No sequence loaded', 'You must load a sequence before starting a multirun.') return 0 results_path = os.path.join(save_results_path, self.ui_param['measure_prefix']) self.appending = False # first check if the measure folder already exists with some files in imax = -1 try: filelist = os.listdir(results_path) for fname in filelist: if 'params' in fname: try: # look for multirun parameters file with open(os.path.join(results_path, fname), 'r') as f: _ = f.readline() vals = f.readline().replace('\n', '').split(';') header = f.readline().replace('\n', '').split(';') params = f.readline().split(';') imax = max( imax, len(vals) + int(params[header.index('1st hist ID')]) - 1) except: pass except (FileNotFoundError, PermissionError): pass # then check the multirun queue for m in self.mr_queue: if self.ui_param['measure_prefix'] == m[0]['measure_prefix']: imax = max(imax, len(m[2]) + m[0]['1st hist ID'] - 1) if self.ui_param['1st hist ID'] == -1: # append at the end self.appending = True self.ui_param['1st hist ID'] = imax + 1 if imax >= 0 else 0 if (os.path.isdir(results_path) or self.ui_param['measure_prefix'] in [ x[0]['measure_prefix'] for x in self.mr_queue ]) and imax >= self.ui_param['1st hist ID']: # this measure exists, check if user wants to overwrite reply = QMessageBox.question( self, 'Confirm Overwrite', "Results path already exists, do you want to overwrite the csv and dat files?\n" + results_path, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.No: if self.appending: # if appending, reset ui_param to -1. Also happens at end of multirun in runid.py self.measures['1st hist ID'].setText('') self.measures['1st hist ID'].setText('-1') return 0 # elif reply == QMessageBox.Yes: # try: # for fn in os.listdir(results_path): # if '.csv' in fn or '.dat' in fn: # os.remove(os.path.join(results_path, fn)) # except Exception as e: # warning('Multirun could not remove files from '+results_dir+'\n'+str(e)) # parameters are valid, add to queue self.mr_queue.append([ copy.deepcopy(self.ui_param), self.tr.copy(), self.get_table(), self.appending ]) if self.appending: # if appending, reset ui_param to -1. Also happens at end of multirun in runid.py self.measures['1st hist ID'].setText('') self.measures['1st hist ID'].setText('-1') if self.suggest_button.isChecked( ): # suggest new multirun measure ID and prefix n = self.ui_param['measure'] + 1 self.measures['measure'].setText(str(n)) self.measures['measure_prefix'].setText('Measure' + str(n)) return 1
class CorrectorViewer(QtGui.QWidget): """ List all corrector and select part to lower table """ def __init__(self, cors, field, parent=None, nmax=4): super(CorrectorViewer, self).__init__(parent) self._nmax = nmax self._field = field self._cors = cors self._corlst1 = QtGui.QTreeWidget() self._header = dict([("Element", 0), ("Family", 1), ("s [m]", 2), ("Alpha X", 3), ("Alpha Y", 4), ("Beta X", 5), ("Beta Y", 6), ("Phi X", 7), ("Phi Y", 8), ("Eta X", 9)]) self._twiss = np.zeros((len(self._cors), 8), 'd') self._tunes = getTunes(source="database") self._corlst1.setColumnCount(len(self._header)) self._corlst1.setHeaderLabels( sorted(self._header, key=self._header.get)) self._corlst1.header().setStretchLastSection(False) prevcell = None for i,c in enumerate(self._cors): if c.cell and (prevcell is None or c.cell != prevcell.text(0)): # a new parent prevcell = QtGui.QTreeWidgetItem() prevcell.setText(0, c.cell) self._corlst1.addTopLevelItem(prevcell) it = QtGui.QTreeWidgetItem() it.setData(0, Qt.UserRole, i) it.setText(self._header["Element"], c.name) it.setText(self._header["Family"], c.family) it.setText(self._header["s [m]"], "%.3f" % c.sb) try: tw = getTwiss(c.name, ["s", "alphax", "alphay", "betax", "betay", "phix", "phiy", "etax"]) self._twiss[i,:] = tw[0,:] it.setText(self._header["Alpha X"], "%.4f" % self._twiss[i,1]) it.setText(self._header["Alpha Y"], "%.4f" % self._twiss[i,2]) it.setText(self._header["Beta X"], "%.4f" % self._twiss[i,3]) it.setText(self._header["Beta Y"], "%.4f" % self._twiss[i,4]) it.setText(self._header["Phi X"], "%.4f" % self._twiss[i,5]) it.setText(self._header["Phi Y"], "%.4f" % self._twiss[i,6]) it.setText(self._header["Eta X"], "%.4f" % self._twiss[i,7]) except: it.setDisabled(True) pass if c.cell: prevcell.addChild(it) else: self._corlst1.addTopLevelItem(it) prevcell = it for j in range(2, len(self._header)): it.setTextAlignment(j, Qt.AlignRight) self._corlst1.expandAll() for i in range(len(self._header)): self._corlst1.resizeColumnToContents(i) #self._corlst1.setColumnWidth(0, 150) #self.elemlst.setSelectionMode(QAbstractItemView.MultiSelection) columns = ['Corrector', 's', 'Alpha', 'Beta', 'Phi', "dPhi", "Initial Bump", "Cur. Sp", "dBump", "Final Rb"] self.table4 = QTableWidget(0, len(columns)) #self.table4.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) hdview = QHeaderView(Qt.Horizontal) self.table4.setHorizontalHeaderLabels(columns) #for i in range(4): # for j in range(len(columns)): # it = QTableWidgetItem() # if j > 0: it.setTextAlignment( # Qt.AlignRight | Qt.AlignVCenter) # if columns[j] != "dKick": # it.setFlags(it.flags() & (~Qt.ItemIsEditable)) # self.table4.setItem(i, j, it) #self.table4.resizeColumnsToContents() #self.table4.horizontalHeader().setStretchLastSection(True) #hrow = self.table4.rowHeight(0) #htbl = (hrow * 4) + self.table4.horizontalHeader().height() +\ # 2*self.table4.frameWidth() #self.table4.setMinimumHeight(htbl + 10) #self.table4.setMaximumHeight(htbl + 15) #self.table4.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) #print "Size:", htbl + 10 self.table4.resize(self.table4.width(), 150) splitter = QtGui.QSplitter(Qt.Vertical) splitter.addWidget(self._corlst1) splitter.addWidget(self.table4) vbox1 = QtGui.QVBoxLayout() vbox1.addWidget(splitter) self.setLayout(vbox1) self.connect(self._corlst1, SIGNAL("doubleClicked(QModelIndex)"), self.addCorrector) #self.connect(self.src, SIGNAL("returnPressed()"), # self._calc_source) #self.connect(self.table4, SIGNAL("cellChanged(int, int)"), # self.updateTable) #self.connect(self.table4, SIGNAL("doubleClicked(QModelIndex)"), # self.delCorrector) self._x0 = fget(self._cors, "x", handle="setpoint", unitsys=None) self._y0 = fget(self._cors, "y", handle="setpoint", unitsys=None) def addCorrector(self, idx): if not self._corlst1.selectedItems(): return #print self._corlst1.itemFromIndex(idx).text(0) nrow = self.table4.rowCount() if nrow >= self._nmax: QtGui.QMessageBox.critical( self, "Local Orbit Bump", "ERROR: We need only {0} correctors.".format(self._nmax), QtGui.QMessageBox.Ok) #self.progress.setValue(0) return self.table4.setRowCount(nrow+1) it0 = self._corlst1.selectedItems()[-1] icor, ok = it0.data(0, Qt.UserRole).toInt() if icor < 0: return newc = self._cors[icor] for j in range(self.table4.columnCount()): it = QTableWidgetItem() if j > 0: it.setTextAlignment( Qt.AlignRight | Qt.AlignVCenter) header = self.table4.horizontalHeaderItem(j) if header.text() != "dBump": it.setFlags(it.flags() & (~Qt.ItemIsEditable)) else: it.setData(Qt.DisplayRole, "0") it.setData(Qt.UserRole, 0.0) self.table4.setItem(nrow, j, it) self.table4.item(nrow,0).setData(Qt.UserRole, icor) for j,h in [(0, "Element"), (1, "s [m]")]: self.table4.item(nrow,j).setData(Qt.DisplayRole, it0.text(self._header[h])) for j in range(self._corlst1.columnCount()): it0.setForeground(j, Qt.red) it0.setDisabled(True) self.emit(SIGNAL("correctorAdded(PyQt_PyObject)"), newc) # use initial values self.updateTwiss() self.updateCorReadings() self.table4.resizeColumnsToContents() if self.table4.rowCount() == self._nmax: #print "All correctors are ready" self.emit(SIGNAL("correctorsComplete()")) def updateTwiss(self): if self._field == "x": jl = [self._header[h] for h in ["Alpha X", "Beta X", "Phi X"]] nu = self._tunes[0] elif self._field == "y": jl = [self._header[h] for h in ["Alpha Y", "Beta Y", "Phi Y"]] nu = self._tunes[1] else: raise RuntimeError("unknown cor field {0}".format(self._field)) #print "index:", jl # if rows provided use it, otherwise use all for i in range(self.table4.rowCount()): elemname = self.table4.item(i,0).data(Qt.DisplayRole).toString() it0 = self._corlst1.findItems( elemname, Qt.MatchExactly | Qt.MatchRecursive)[0] self.table4.item(i,2).setText(it0.text(jl[0])) self.table4.item(i,3).setText(it0.text(jl[1])) self.table4.item(i,4).setText(it0.text(jl[2])) self.table4.item(i,4).setData(Qt.UserRole, float(it0.text(jl[2]))) if i == 0: self.table4.item(i,5).setText("0.0") self.table4.item(i,5).setData(Qt.UserRole, 0.0) else: dph, ok = self.table4.item(i-1,5).data(Qt.UserRole).toFloat() ph0, ok = self.table4.item(i-1,4).data(Qt.UserRole).toFloat() ph1, ok = self.table4.item(i,4).data(Qt.UserRole).toFloat() dph = dph + ph1 - ph0 if ph1 < ph0: dph = dph + 2.0*np.pi*nu #print "Updating twiss:", i, dph self.table4.item(i,5).setData(Qt.UserRole, dph) self.table4.item(i,5).setText("%.5g" % dph) icor, ok = self.table4.item(i,0).data(Qt.UserRole).toInt() #c = self._cors[icor] #kick = self._cors[icor].get(self._field, unitsys=None) #self.table4.item(i,6).setData(Qt.UserRole, kick) #self.table4.item(i,6).setText("%.5g" % kick) #self.updateKickReadings(col=0) def clear(self): for i in range(self.table4.rowCount()): elemname = self.table4.item(i,0).data(Qt.DisplayRole).toString() it0 = self._corlst1.findItems( elemname, Qt.MatchExactly | Qt.MatchRecursive)[0] it0.setDisabled(False) for j in range(self._corlst1.columnCount()): it0.setForeground(j, Qt.black) self.table4.setRowCount(0) def updateCorReadings(self): for i in range(self.table4.rowCount()): icor, ok = self.table4.item(i,0).data(Qt.UserRole).toInt() cor = self._cors[icor] if self._field == "x": self.table4.item(i,6).setData(Qt.UserRole, self._x0[icor]) self.table4.item(i,6).setText("%.5g" % self._x0[icor]) elif self._field == "y": self.table4.item(i,6).setData(Qt.UserRole, self._y0[icor]) self.table4.item(i,6).setText("%.5g" % self._y0[icor]) kicksp = cor.get(self._field, handle="setpoint", unitsys=None) self.table4.item(i,7).setData(Qt.UserRole, float(kicksp)) self.table4.item(i,7).setText("%.5g" % kicksp) kickrb = cor.get(self._field, handle="readback", unitsys=None) self.table4.item(i,9).setData(Qt.UserRole, float(kickrb)) self.table4.item(i,9).setText("%.5g" % kickrb) def updateDbump(self, dkick): nrow = min(self.table4.rowCount(), len(dkick)) for i in range(nrow): # dbump column is 8 it = self.table4.item(i, 8) if dkick[i] is None: it.setData(Qt.DisplayRole, "") it.setData(Qt.UserRole, 0.0) else: it.setData(Qt.UserRole, float(dkick[i])) it.setData(Qt.DisplayRole, "{0}".format(dkick[i])) self.updateCorReadings() self.table4.resizeColumnsToContents() #print "(0,7)", self.table4.item(0, 7).data(Qt.UserRole).toFloat() #print "(0,7)", self.table4.item(0, 7).data(Qt.DisplayRole).toFloat() def applyKick(self): nrow = self.table4.rowCount() for i in range(nrow): icor, ok = self.table4.item(i,0).data(Qt.UserRole).toInt() cor = self._cors[icor] # Current SP: 7, dBump 8 k0, ok = self.table4.item(i, 7).data(Qt.UserRole).toFloat() dk, ok = self.table4.item(i, 8).data(Qt.UserRole).toFloat() #print "Setting {0} {1}+{2} [A]".format(cor.name, k0, dk) cor.put(self._field, k0+dk, unitsys=None) # update the final readings self.updateCorReadings() def getTwiss(self): tw = {"s": [], "Alpha": [], "Beta": [], "Phi": [], "dPhi": []} nrow = self.table4.rowCount() for j in range(self.table4.columnCount()): header = self.table4.horizontalHeaderItem(j) if header.text() not in tw.keys(): continue k = str(header.text()) for i in range(nrow): it = self.table4.item(i, j) v0, ok0 = it.data(Qt.UserRole).toFloat() v1, ok1 = it.data(Qt.DisplayRole).toFloat() if ok0: tw[k].append(v0) elif ok1: tw[k].append(v1) else: tw[k].append(np.nan) return tw def selectedCorrectors(self): ret = [] for i in range(self.table4.rowCount()): icor, ok = self.table4.item(i,0).data(Qt.UserRole).toInt() ret.append(self._cors[icor]) return ret def resetCorrectors(self): for i in range(self.table4.rowCount()): icor, ok = self.table4.item(i,0).data(Qt.UserRole).toInt() cor = self._cors[icor] if self._field == "x": kick = self._x0[icor] elif self._field == "y": kick = self._y0[icor] cor.put(self._field, kick, unitsys=None)
class daq_window(QMainWindow): """Window to control and visualise DAQ measurements. Set up the desired channels with a given sampling rate. Start an acquisition after a trigger. Display the acquired data on a trace, and accumulated data on a graph. Arguments: n -- run number for synchronisation rate -- max sample rate in samples / second dt -- desired acquisition period in seconds config_file -- path to file storing default settings port -- the port number to open for TCP connections """ def __init__(self, n=0, rate=250, dt=500, config_file='monitor\\daqconfig.dat', port=8622): super().__init__() self.types = OrderedDict([('n', int), ('config_file', str), ('trace_file', str), ('graph_file', str), ('save_dir', str), ('Sample Rate (kS/s)',float), ('Duration (ms)', float), ('Trigger Channel', str), ('Trigger Level (V)', float), ('Trigger Edge', str), ('channels',channel_stats)]) self.stats = OrderedDict([('n', n), ('config_file', config_file), ('trace_file', 'DAQtrace.csv'), ('graph_file', 'DAQgraph.csv'),('save_dir', '.'), ('Sample Rate (kS/s)', rate), ('Duration (ms)', dt), ('Trigger Channel', 'Dev2/ai1'), # /Dev2/PFI0 ('Trigger Level (V)', 1.0), ('Trigger Edge', 'rising'), ('channels', channel_stats("[['Dev2/ai0', '0', '1.0', '0.0', '5', '1', '1']]"))]) self.trigger_toggle = True # whether to trigger acquisition or just take a measurement self.slave = worker(rate*1e3, dt/1e3, self.stats['Trigger Channel'], self.stats['Trigger Level (V)'], self.stats['Trigger Edge'], list(self.stats['channels'].keys()), [ch['range'] for ch in self.stats['channels'].values()]) # this controls the DAQ self.dc = daqCollection(param=[], channels=list(self.stats['channels'].keys())) self.init_UI() self.load_config(config_file) # load default settings self.n_samples = int(self.stats['Duration (ms)'] * self.stats['Sample Rate (kS/s)']) # number of samples per acquisition self.last_path = './' self.x = [] # run numbers for graphing collections of acquired data self.y = [] # average voltages in slice of acquired trace self.slave.acquired.connect(self.update_graph) # take average of slices self.slave.acquired.connect(self.update_trace) # plot new data when it arrives self.tcp = PyClient(port=port) remove_slot(self.tcp.dxnum, self.set_n, True) remove_slot(self.tcp.textin, self.respond, True) self.tcp.start() def init_UI(self): """Produce the widgets and buttons.""" self.centre_widget = QWidget() self.tabs = QTabWidget() # make tabs for each main display self.centre_widget.layout = QVBoxLayout() self.centre_widget.layout.addWidget(self.tabs) self.centre_widget.setLayout(self.centre_widget.layout) self.setCentralWidget(self.centre_widget) # change font size font = QFont() font.setPixelSize(18) #### menubar at top gives options #### menubar = self.menuBar() # file menubar allows you to save/load data file_menu = menubar.addMenu('File') for label, function in [['Load Config', self.load_config], ['Save Config', self.save_config], ['Load Trace', self.load_trace], ['Save Trace', self.save_trace], ['Save Graph', self.save_graph]]: action = QAction(label, self) action.triggered.connect(function) file_menu.addAction(action) #### tab for settings #### settings_tab = QWidget() settings_grid = QGridLayout() settings_tab.setLayout(settings_grid) self.tabs.addTab(settings_tab, "Settings") self.settings = QTableWidget(1, 6) self.settings.setHorizontalHeaderLabels(['Duration (ms)', 'Sample Rate (kS/s)', 'Trigger Channel', 'Trigger Level (V)', 'Trigger Edge', 'Use Trigger?']) settings_grid.addWidget(self.settings, 0,0, 1,1) defaults = [str(self.stats['Duration (ms)']), str(self.stats['Sample Rate (kS/s)']), self.stats['Trigger Channel'], str(self.stats['Trigger Level (V)']), self.stats['Trigger Edge'], '1'] validators = [double_validator, double_validator, None, double_validator, None, bool_validator] for i in range(6): table_item = QLineEdit(defaults[i]) # user can edit text to change the setting if defaults[i] == 'Sample Rate (kS/s)': table_item.setEnabled(False) table_item.setValidator(validators[i]) # validator limits the values that can be entered self.settings.setCellWidget(0,i, table_item) self.settings.resizeColumnToContents(1) self.settings.setFixedHeight(70) # make it take up less space self.settings.cellWidget(0,0).textChanged.connect(self.check_slice_duration) # start/stop: start waiting for a trigger or taking an acquisition self.toggle = QPushButton('Start', self) self.toggle.setCheckable(True) self.toggle.clicked.connect(self.activate) settings_grid.addWidget(self.toggle, 1,0, 1,1) # channels self.channels = QTableWidget(8, 7) # make table self.channels.setHorizontalHeaderLabels(['Channel', 'Label', 'Scale (X/V)', 'Offset (V)', 'Range', 'Acquire?', 'Plot?']) settings_grid.addWidget(self.channels, 2,0, 1,1) validators = [None, double_validator, double_validator, None, bool_validator, bool_validator] for i in range(8): chan = 'Dev2/ai'+str(i) # name of virtual channel table_item = QLabel(chan) self.channels.setCellWidget(i,0, table_item) if chan in self.stats['channels']: # load values from previous defaults = self.stats['channels'][chan] else: # default values when none are loaded defaults = channel_stats("[dummy, "+str(i)+", 1.0, 0.0, 5.0, 0, 0]")['dummy'] for j, key in zip([0,1,2,4,5], ['label', 'scale', 'offset', 'acquire', 'plot']): table_item = QLineEdit(str(defaults[key])) if 'acquire' in key: table_item.textChanged.connect(self.check_slice_channels) elif 'plot' in key: table_item.textChanged.connect(self.set_acquire) table_item.setValidator(validators[j]) self.channels.setCellWidget(i,j+1, table_item) vrange = QComboBox() # only allow certain values for voltage range vrange.text = vrange.currentText # overload function so it's same as QLabel vrange.addItems(['%.1f'%x for x in self.slave.vrs]) try: vrange.setCurrentIndex(self.slave.vrs.index(defaults['range'])) except Exception as e: logger.error('Invalid channel voltage range\n'+str(e)) self.channels.setCellWidget(i,4, vrange) #### Plot for most recently acquired trace #### trace_tab = QWidget() trace_grid = QGridLayout() trace_tab.setLayout(trace_grid) self.tabs.addTab(trace_tab, "Trace") # button activates horizontal line self.hline_toggle = QPushButton('Horizontal line', self, checkable=True) self.hline_toggle.clicked.connect(self.add_horizontal) trace_grid.addWidget(self.hline_toggle, 0,0, 1,1) self.hline_label = QLabel() trace_grid.addWidget(self.hline_label, 0,1, 1,1) fadeline_button = QPushButton('Persist', self) fadeline_button.clicked.connect(self.set_fadelines) trace_grid.addWidget(fadeline_button, 0,2, 1,1) # plot the trace self.trace_canvas = pg.PlotWidget() self.trace_legend = self.trace_canvas.addLegend() self.trace_canvas.getAxis('bottom').tickFont = font self.trace_canvas.getAxis('left').tickFont = font self.trace_canvas.setLabel('bottom', 'Time', 's', **{'font-size':'18pt'}) self.trace_canvas.setLabel('left', 'Voltage', 'V', **{'font-size':'18pt'}) self.lines = [] # handles for lines plotting the last measurement self.fadelines = [] # handles for previous measurement lines for i in range(8): chan = self.channels.cellWidget(i,1).text() self.lines.append(self.trace_canvas.plot([1], name=chan, pen=pg.mkPen(pg.intColor(i), width=3))) self.lines[i].hide() self.fadelines.append(self.trace_canvas.plot([1], pen=pg.mkPen(pg.intColor(i, alpha=50), width=2))) self.fadelines[i].hide() self.hline = pg.InfiniteLine(1., angle=0, pen='k', movable=True) self.trace_canvas.addItem(self.hline) self.hline.sigPositionChanged.connect(self.update_hline) self.hline.hide() trace_grid.addWidget(self.trace_canvas, 1,0, 1,3) #### Settings for slices of the trace accumulating into the graph #### slice_tab = QWidget() slice_grid = QGridLayout() slice_tab.setLayout(slice_grid) self.tabs.addTab(slice_tab, "Slice") # Buttons to add/remove slices and reset graph for i, (label, func) in enumerate([['Add slice', self.add_slice], ['Remove slice', self.del_slice], ['Reset graph', self.reset_graph]]): button = QPushButton(label, self) button.clicked.connect(func) slice_grid.addWidget(button, 0,i, 1,1) # parameters for slices self.slices = QTableWidget(0, 4) # make table self.slices.setHorizontalHeaderLabels(['Slice name', 'Start (ms)', 'End (ms)', 'Channels']) slice_grid.addWidget(self.slices, 1,0, 1,3) #### Plot for graph of accumulated data #### graph_tab = QWidget() graph_grid = QGridLayout() graph_tab.setLayout(graph_grid) self.tabs.addTab(graph_tab, "Graph") self.mean_graph = pg.PlotWidget() # for plotting means self.stdv_graph = pg.PlotWidget() # for plotting standard deviations self.graph_legends = [] for i, g in enumerate([self.mean_graph, self.stdv_graph]): g.getAxis('bottom').tickFont = font g.getAxis('bottom').setFont(font) g.getAxis('left').tickFont = font g.getAxis('left').setFont(font) graph_grid.addWidget(g, i,0, 1,1) self.reset_lines() # make a line for every slice channel self.stdv_graph.setLabel('bottom', 'Shot', '', **{'font-size':'18pt'}) self.stdv_graph.setLabel('left', 'Standard Deviation', 'V', **{'font-size':'18pt'}) self.mean_graph.setLabel('left', 'Mean', 'V', **{'font-size':'18pt'}) #### tab for TCP message settings #### tcp_tab = QWidget() tcp_grid = QGridLayout() tcp_tab.setLayout(tcp_grid) self.tabs.addTab(tcp_tab, "Sync") label = QLabel('Run number: ') tcp_grid.addWidget(label, 0,0, 1,1) self.n_edit = QLineEdit(str(self.stats['n'])) self.n_edit.setValidator(int_validator) self.n_edit.textEdited[str].connect(self.set_n) tcp_grid.addWidget(self.n_edit, 0,1, 1,1) label = QLabel('Save directory: ') tcp_grid.addWidget(label, 1,0, 1,1) self.save_edit = QLineEdit(self.stats['save_dir']) self.save_edit.textEdited[str].connect(self.set_save_dir) tcp_grid.addWidget(self.save_edit, 1,1, 1,1) label = QLabel('Trace file name: ') tcp_grid.addWidget(label, 2,0, 1,1) self.trace_edit = QLineEdit(self.stats['trace_file']) self.trace_edit.textEdited[str].connect(self.set_trace_file) tcp_grid.addWidget(self.trace_edit, 2,1, 1,1) label = QLabel('Graph file name: ') tcp_grid.addWidget(label, 3,0, 1,1) self.graph_edit = QLineEdit(self.stats['graph_file']) self.graph_edit.textEdited[str].connect(self.set_graph_file) tcp_grid.addWidget(self.graph_edit, 3,1, 1,1) reset = QPushButton('Reset TCP client', self) reset.clicked.connect(self.reset_client) tcp_grid.addWidget(reset, 4,0, 1,1) #### Title and icon #### self.setWindowTitle('- NI DAQ Controller -') self.setWindowIcon(QIcon('docs/daqicon.png')) self.setGeometry(200, 200, 800, 600) #### user input functions #### def set_acquire(self): """If the user chooses to plot, set the same channel to acquire.""" for i in range(self.channels.rowCount()): if BOOL(self.channels.cellWidget(i,6).text()): # plot self.channels.cellWidget(i,5).setText('1') # only plot if acquiring def check_settings(self): """Coerce the settings into allowed values.""" statstr = "[[" # dictionary of channel names and properties for i in range(self.channels.rowCount()): self.trace_legend.items[i][1].setText(self.channels.cellWidget(i,1).text()) # label if BOOL(self.channels.cellWidget(i,5).text()): # acquire statstr += ', '.join([self.channels.cellWidget(i,j).text() for j in range(self.channels.columnCount())]) + '],[' self.stats['channels'] = channel_stats(statstr[:-2] + ']') self.dc.channels = self.stats['channels'].keys() # acquisition settings self.stats['Duration (ms)'] = float(self.settings.cellWidget(0,0).text()) # check that the requested rate is valid rate = float(self.settings.cellWidget(0,1).text()) if len(self.stats['channels']) > 1 and rate > 245 / len(self.stats['channels']): rate = 245 / len(self.stats['channels']) elif len(self.stats['channels']) < 2 and rate > 250: rate = 250 self.stats['Sample Rate (kS/s)'] = rate self.settings.cellWidget(0,1).setText('%.2f'%(rate)) self.n_samples = int(self.stats['Duration (ms)'] * self.stats['Sample Rate (kS/s)']) # check the trigger channel is valid trig_chan = self.settings.cellWidget(0,2).text() if 'Dev2/PFI' in trig_chan or 'Dev2/ai' in trig_chan: self.stats['Trigger Channel'] = trig_chan else: self.stats['Trigger Channel'] = 'Dev2/ai0' self.settings.cellWidget(0,2).setText(str(self.stats['Trigger Channel'])) self.stats['Trigger Level (V)'] = float(self.settings.cellWidget(0,3).text()) self.stats['Trigger Edge'] = self.settings.cellWidget(0,4).text() self.trigger_toggle = BOOL(self.settings.cellWidget(0,5).text()) def set_table(self): """Display the acquisition and channel settings in the table.""" x = self.stats.copy() # prevent it getting overwritten for i in range(5): self.settings.cellWidget(0,i).setText(str(x[ self.settings.horizontalHeaderItem(i).text()])) for i in range(8): ch = self.channels.cellWidget(i,0).text() if ch in x['channels']: for j, key in zip([0,1,2,4,5], ['label', 'scale', 'offset', 'acquire', 'plot']): self.channels.cellWidget(i,j+1).setText(str(x['channels'][ch][key])) self.channels.cellWidget(i,4).setCurrentText('%.1f'%x['channels'][ch]['range']) else: self.channels.cellWidget(i,5).setText('0') # don't acquire self.channels.cellWidget(i,6).setText('0') # don't plot #### slice settings functions #### def add_slice(self, param=[]): """Add a row to the slice table and append it to the daqCollection instance for analysis. param -- [name, start (ms), end (ms), channels]""" try: name, start, end, channels = param except TypeError: name = 'Slice' + str(self.slices.rowCount()) start = 0 end = self.stats['Duration (ms)'] channels = list(self.stats['channels'].keys()) i = self.slices.rowCount() # index to add row at self.slices.insertRow(i) # add row to table validator = QDoubleValidator(0.,float(self.stats['Duration (ms)']),3) for j, text in enumerate([name, str(start), str(end)]): item = QLineEdit(text) item.pos = (i, j) if j > 0: item.setValidator(validator) item.textChanged.connect(self.update_slices) self.slices.setCellWidget(i, j, item) chanbox = QListWidget(self) chanbox.setSelectionMode(3) # extended selection, allows multiple selection chanbox.itemSelectionChanged.connect(self.update_slices) chanbox.pos = (i, 3) chanbox.text = chanbox.objectName chanlist = list(self.stats['channels'].keys()) chanbox.addItems(chanlist) self.slices.setCellWidget(i, j+1, chanbox) self.slices.resizeRowToContents(i) # add to the dc list of slices t = np.linspace(0, self.stats['Duration (ms)']/1000, self.n_samples) self.dc.add_slice(name, np.argmin(np.abs(t-start)), np.argmin(np.abs(t-end)), OrderedDict([(chan, chanlist.index(chan)) for chan in channels])) self.reset_lines() def del_slice(self, toggle=True): """Remove the slice at the selected row of the slice table.""" index = self.slices.currentRow() self.slices.removeRow(index) if index >= 0: self.dc.slices.pop(index) def check_slice_duration(self, newtxt): """If the acquisition duration is changed, make sure the slices can't use times beyond this.""" self.check_settings() # update DAQ acquisition settings first for i in range(self.slices.rowCount()): for j in [1,2]: # force slice to be within max duration validator = QDoubleValidator(0.,float(self.stats['Duration (ms)']),3) self.slices.cellWidget(i, j).setValidator(validator) def check_slice_channels(self, newtxt): """If the channels that can be used for the acquisition are changed, change the list widgets in the slice settings to match.""" self.check_settings() # update DAQ acquisition settings first for i in range(self.slices.rowCount()): w = self.slices.cellWidget(i, 3) # widget shorthand selected = [w.row(x) for x in w.selectedItems()] # make record of selected channels w.clear() w.addItems(list(self.stats['channels'].keys())) for r in selected: try: w.setCurrentRow(r, QItemSelectionModel.SelectCurrent) except Exception as e: pass def update_slices(self, newtxt=''): """Use the current item from the table to update the parameter for the slices.""" try: w = self.sender() i, j = w.pos t = np.linspace(0, self.stats['Duration (ms)'], self.n_samples) x = self.dc.slices[i] # shorthand if j == 0: # name x.name = w.text() elif j == 1: # start (ms) x.i0 = np.argmin(np.abs(t-float(w.text()))) elif j == 2: # end (ms) x.i1 = np.argmin(np.abs(t-float(w.text()))) elif j == 3: x.channels = OrderedDict([(x.text(), w.row(x)) for x in w.selectedItems()]) x.stats = OrderedDict([(chan, OrderedDict([ ('mean',[]), ('stdv',[])])) for chan in x.channels.keys()]) self.reset_lines() self.reset_graph() x.inds = slice(x.i0, x.i1+1) x.size = x.i1 - x.i0 except IndexError as e: pass # logger.error("Couldn't update slice.\n"+str(e)) #### TCP functions #### def set_n(self, num): """Receive the new run number to update to""" self.stats['n'] = int(num) self.n_edit.setText(str(num)) def set_save_dir(self, directory): """Set the directory to save results to""" self.stats['save_dir'] = directory self.save_edit.setText(directory) def set_trace_file(self, fname): """Set the default name for trace files when they're saved.""" self.stats['trace_file'] = fname self.trace_edit.setText(fname) def set_graph_file(self, fname): """Set the default name for graph files when they're saved.""" self.stats['graph_file'] = fname self.graph_edit.setText(fname) def reset_client(self, toggle=True): """Stop the TCP client thread then restart it.""" self.tcp.stop = True for i in range(100): # wait til it's stopped if not self.tcp.isRunning(): break else: time.sleep(0.001) self.tcp.start() # restart def respond(self, msg=''): """Interpret a TCP message. For setting properties, the syntax is: value=property. E.g. 'Z:\Tweezer=save_dir'.""" if 'save_dir' in msg: self.set_save_dir(msg.split('=')[0]) elif 'trace_file' in msg: self.set_trace_file(msg.split('=')[0]) elif 'graph_file' in msg: self.set_graph_file(msg.split('=')[0]) elif 'start' in msg and not self.toggle.isChecked(): self.toggle.setChecked(True) self.activate() elif 'stop' in msg and self.toggle.isChecked(): self.toggle.setChecked(False) self.activate() elif 'save trace' in msg: self.save_trace(os.path.join(self.stats['save_dir'], self.stats['trace_file'])) elif 'save graph' in msg: self.save_graph(os.path.join(self.stats['save_dir'], self.stats['graph_file'])) elif 'set fadelines' in msg: self.set_fadelines() #### acquisition functions #### def activate(self, toggle=0): """Prime the DAQ task for acquisition if it isn't already running. Otherwise, stop the task running.""" if self.toggle.isChecked(): self.check_settings() self.slave = worker(self.stats['Sample Rate (kS/s)']*1e3, self.stats['Duration (ms)']/1e3, self.stats['Trigger Channel'], self.stats['Trigger Level (V)'], self.stats['Trigger Edge'], list(self.stats['channels'].keys()), [ch['range'] for ch in self.stats['channels'].values()]) remove_slot(self.slave.acquired, self.update_trace, True) remove_slot(self.slave.acquired, self.update_graph, True) if self.trigger_toggle: # remove_slot(self.slave.finished, self.activate, True) self.slave.start() self.toggle.setText('Stop') else: self.toggle.setChecked(False) self.slave.analogue_acquisition() else: # remove_slot(self.slave.finished, self.activate, False) self.slave.stop = True self.slave.quit() self.toggle.setText('Start') #### plotting functions #### def update_trace(self, data): """Plot the supplied data with labels on the trace canvas.""" t = np.linspace(0, self.stats['Duration (ms)']/1000, self.n_samples) i = 0 # index to keep track of which channels have been plotted for j in range(8): ch = self.channels.cellWidget(j,0).text() l = self.lines[j] # shorthand if ch in self.stats['channels'] and self.stats['channels'][ch]['plot']: try: l.setData(t, data[i]) except Exception as e: logger.error('DAQ trace could not be plotted.\n'+str(e)) self.fadelines[j].show() l.show() self.trace_legend.items[j][0].show() self.trace_legend.items[j][1].show() i += 1 else: l.hide() self.fadelines[j].hide() self.trace_legend.items[j][0].hide() self.trace_legend.items[j][1].hide() self.trace_legend.resize(0,0) def set_fadelines(self): """Take the data from the current lines and sets it to the fadelines.""" for j in range(8): ch = self.channels.cellWidget(j,0).text() l = self.lines[j] # shorthand if ch in self.stats['channels'] and self.stats['channels'][ch]['plot']: try: self.fadelines[j].setData(l.xData, l.yData) except Exception as e: logger.error('DAQ trace could not be plotted.\n'+str(e)) self.fadelines[j].show() else: self.fadelines[j].hide() def reset_lines(self): """Clear the mean and stdv graphs, reset the legends, then make new lines for each of the slice channels.""" for legend in self.graph_legends: # reset the legends try: legend.scene().removeItem(legend) except AttributeError: pass for g in [self.mean_graph, self.stdv_graph]: g.clear() g.lines = OrderedDict([]) self.graph_legends.append(g.addLegend()) i = 0 for s in self.dc.slices: for chan, val in s.stats.items(): g.lines[s.name+'/'+chan] = g.plot([1], name=s.name+'/'+chan, pen=None, symbol='o', symbolPen=pg.mkPen(pg.intColor(i)), symbolBrush=pg.intColor(i)) i += 1 def reset_graph(self): """Reset the collection of slice data, then replot the graph.""" self.dc.reset_arrays() self.update_graph() def update_graph(self, data=[]): """Extract averages from slices of the data. Replot the stored data accumulated from averages in slices of the measurements.""" if np.size(data): self.dc.process(data, self.stats['n']) for s in self.dc.slices: for chan, val in s.stats.items(): self.mean_graph.lines[s.name+'/'+chan].setData(self.dc.runs, val['mean']) self.stdv_graph.lines[s.name+'/'+chan].setData(self.dc.runs, val['stdv']) def add_horizontal(self, toggle=True): """Display a horizontal line on the trace""" if toggle: self.hline.show() else: self.hline.hide() self.hline_label.setText('') def update_hline(self): """Display the value of the horizontal line in the label""" self.hline_label.setText(str(self.hline.value())) #### save/load functions #### def try_browse(self, title='Select a File', file_type='all (*)', open_func=QFileDialog.getOpenFileName, default_path=''): """Open a file dialog and retrieve a file name from the browser. title: String to display at the top of the file browser window default_path: directory to open first file_type: types of files that can be selected open_func: the function to use to open the file browser""" default_path = default_path if default_path else os.path.dirname(self.last_path) try: if 'PyQt4' in sys.modules: file_name = open_func(self, title, default_path, file_type) elif 'PyQt5' in sys.modules: file_name, _ = open_func(self, title, default_path, file_type) if type(file_name) == str: self.last_path = file_name return file_name except OSError: return '' # probably user cancelled def save_config(self, file_name='daqconfig.dat'): """Save the current acquisition settings to the config file.""" self.stats['config_file'] = file_name if file_name else self.try_browse( 'Save Config File', 'dat (*.dat);;all (*)', QFileDialog.getSaveFileName) try: with open(self.stats['config_file'], 'w+') as f: for key, val in self.stats.items(): if key == 'channels': f.write(key+'='+channel_str(val)+'\n') else: f.write(key+'='+str(val)+'\n') logger.info('DAQ config saved to '+self.stats['config_file']) except Exception as e: logger.error('DAQ settings could not be saved to config file.\n'+str(e)) def load_config(self, file_name='daqconfig.dat'): """Load the acquisition settings from the config file.""" self.stats['config_file'] = file_name if file_name else self.try_browse(file_type='dat (*.dat);;all (*)') try: with open(self.stats['config_file'], 'r') as f: for line in f: if len(line.split('=')) == 2: key, val = line.replace('\n','').split('=') # there should only be one = per line try: self.stats[key] = self.types[key](val) except KeyError as e: logger.warning('Failed to load DAQ default config line: '+line+'\n'+str(e)) self.set_table() # make sure the updates are displayed self.set_n(self.stats['n']) self.set_save_dir(self.stats['save_dir']) self.set_trace_file(self.stats['trace_file']) self.set_graph_file(self.stats['graph_file']) self.dc.channels = list(self.stats['channels'].keys()) logger.info('DAQ config loaded from '+self.stats['config_file']) except FileNotFoundError as e: logger.warning('DAQ settings could not find the config file.\n'+str(e)) def save_trace(self, file_name=''): """Save the data currently displayed on the trace to a csv file.""" file_name = file_name if file_name else self.try_browse( 'Save File', 'csv (*.csv);;all (*)', QFileDialog.getSaveFileName) if file_name: # metadata header = ', '.join(list(self.stats.keys())) + '\n' header += ', '.join(list(map(str, self.stats.values()))[:-1] ) + ', ' + channel_str(self.stats['channels']) + '\n' # determine which channels are in the plot header += 'Time (s)' data = [] for key, d in self.stats['channels'].items(): if d['plot']: header += ', ' + key # column headings if len(data) == 0: # time (s) data.append(self.lines[int(key[-1])].xData) data.append(self.lines[int(key[-1])].yData) # voltage # data converted to the correct type out_arr = np.array(data).T try: np.savetxt(file_name, out_arr, fmt='%s', delimiter=',', header=header) logger.info('DAQ trace saved to '+file_name) except (PermissionError, FileNotFoundError) as e: logger.error('DAQ controller denied permission to save file: \n'+str(e)) def load_trace(self, file_name=''): """Load data for the current trace from a csv file.""" file_name = file_name if file_name else self.try_browse(file_type='csv(*.csv);;all (*)') if file_name: head = [[],[],[]] # get metadata with open(file_name, 'r') as f: for i in range(3): row = f.readline() if row[:2] == '# ': head[i] = row[2:].replace('\n','').split(', ') # apply the acquisition settings from the file labels = [self.settings.horizontalHeaderItem(i).text() for i in range(self.settings.columnCount())] for i in range(len(head[0])): try: j = labels.index(head[0][i]) self.settings.cellWidget(0,j).setText(head[1][i]) except ValueError: pass self.stats['channels'] = channel_stats(', '.join(head[1][7:])) for i in range(8): # whether to plot or not ch = self.channels.cellWidget(i,0).text() if ch in head[2]: self.channels.cellWidget(i,6).setText('1') self.channels.cellWidget(i,1).setText(self.stats['channels'][ch]['label']) else: self.channels.cellWidget(i,6).setText('0') self.check_settings() # plot the data data = np.genfromtxt(file_name, delimiter=',', dtype=float) if np.size(data) < 2: return 0 # insufficient data to load self.update_trace(data.T[1:]) def save_graph(self, file_name=''): """Save the data accumulated from several runs that's displayed in the graph into a csv file.""" file_name = file_name if file_name else self.try_browse( 'Save File', 'csv (*.csv);;all (*)', QFileDialog.getSaveFileName) if file_name: self.dc.save(file_name, list(self.stats.keys()), list(map(str, self.stats.values()))[:-1] + [channel_str(self.stats['channels'])]) logger.info('DAQ graph saved to '+file_name) def closeEvent(self, event): """Before closing, try to save the config settings to file.""" statstr = "[[" # dictionary of channel names and properties for i in range(self.channels.rowCount()): statstr += ', '.join([self.channels.cellWidget(i,j).text() for j in range(self.channels.columnCount())]) + '],[' self.stats['channels'] = channel_stats(statstr[:-2] + ']') # add all channels to stats self.save_config(self.stats['config_file']) event.accept()
class NewRelationDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(self.tr("Nueva Relación")) vbox = QVBoxLayout(self) hbox = QHBoxLayout() self._line_relation_name = QLineEdit() hbox.addWidget(QLabel(self.tr("Nombre:"))) hbox.addWidget(self._line_relation_name) vbox.addLayout(hbox) vbox.addWidget(QLabel( self.tr("La primera fila corresponde a los campos"))) hbox = QHBoxLayout() btn_add_column = QPushButton(self.tr("Agregar Columna")) hbox.addWidget(btn_add_column) btn_add_tuple = QPushButton(self.tr("Agregar Tupla")) hbox.addWidget(btn_add_tuple) btn_remove_column = QPushButton(self.tr("Eliminar Columna")) hbox.addWidget(btn_remove_column) btn_remove_tuple = QPushButton(self.tr("Eliminar Tupla")) hbox.addWidget(btn_remove_tuple) vbox.addLayout(hbox) self._table = QTableWidget() vbox.addWidget(self._table) self._table.setRowCount(1) self._table.setColumnCount(2) self._table.setItem(0, 0, QTableWidgetItem("Campo 1")) self._table.setItem(0, 1, QTableWidgetItem("Campo 2")) hbox = QHBoxLayout() hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_ok = QPushButton(self.tr("Aceptar")) hbox.addWidget(btn_ok) btn_cancel = QPushButton(self.tr("Cancelar")) hbox.addWidget(btn_cancel) vbox.addLayout(hbox) # Connections self.connect(btn_add_column, SIGNAL("clicked()"), self.__add_column) self.connect(btn_remove_column, SIGNAL("clicked()"), self.__remove_column) self.connect(btn_add_tuple, SIGNAL("clicked()"), self.__add_tuple) self.connect(btn_remove_tuple, SIGNAL("clicked()"), self.__remove_tuple) self.connect(btn_ok, SIGNAL("clicked()"), self.__create_table) self.connect(btn_cancel, SIGNAL("clicked()"), self.close) def __add_column(self): columns = self._table.columnCount() self._table.insertColumn(columns) def __remove_column(self): current = self._table.currentColumn() self._table.removeColumn(current) def __add_tuple(self): tuples = self._table.rowCount() self._table.insertRow(tuples) def __remove_tuple(self): current = self._table.currentRow() self._table.removeRow(current) def __create_table(self): name = self._line_relation_name.text() rows = self._table.rowCount() columns = self._table.columnCount() rel = relation.Relation() fields = [self._table.item(0, i).text() for i in range(columns)] rel.fields = fields data = {} for row in range(1, rows): reg = [] for column in range(columns): reg.append(self._table.item(row, column).text()) data[row, column] = self._table.item(row, column).text() rel.insert(reg) table_widget = Pireal.get_service("container").table_widget table_widget.add_table(rows - 1, columns, name, data) #table_widget.relations[name] = rel self.close()
class edycja_proby(QDialog): def __init__(self, sample, defpol={}, defpol_order=[], parent=None): ''' defpol and defpol_order should contains the same values, it will not be checked in this class is it valid, pay attention to it''' super(edycja_proby, self).__init__(parent) # self.dane = globals()["daneW"][0] # self.defpol = globals()["daneW"][1] # globals()["daneW"] = [] self.sample = copy.deepcopy(sample) self.sample._edytowany = 'E' self.org_sample = sample self.newName = self.sample.KeyCode() self.setWindowTitle("Sample: " + self.sample.KeyCode()) self.resize(691, 749) self.setMinimumSize(QSize(691, 749)) # self.setMaximumSize(QSize(691, 749)) # Prepare headers self.defpol = defpol self.defpol_order = defpol_order self.prepareDefPol() self.selected = [] # list with index of selected ring in sample # ustawienie pol z danymi self.p_naglowek = QTableWidget() self.p_naglowek.setObjectName("p_naglowek") self.p_naglowek.setColumnCount(1) self.p_naglowek.setRowCount(len(self.headers)) self.p_naglowek.setHorizontalHeaderLabels(["Value"]) self.p_naglowek.setVerticalHeaderLabels(self.headers) self.p_naglowek.setAlternatingRowColors(True) self.p_naglowek.setSortingEnabled(False) self.p_dane = QTableWidget() self.p_dane.setObjectName("p_dane") self.p_dane.setColumnCount(10) self.p_dane.setHorizontalHeaderLabels( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) self.dodaj = QPushButton("Add") self.usun = QPushButton("Delete") self.przerysuj = QPushButton("Redraw") self.podziel = QPushButton("Divide") self.polacz = QPushButton("Join") self.anuluj = QPushButton("Cancel") self.wykonaj = QPushButton("Update") self.vbl = QVBoxLayout() self.qmc = Qt4MplCanvas(self) self.ntb = NavigationToolbar(self.qmc, self) self.vbl.addWidget(self.qmc) self.vbl.addWidget(self.ntb) self.updateHeaderTable() self.wpisz_pomiary() self.przerysuj_wykres() # ustawiamy wyglad okna layout = QGridLayout() layout.addWidget(self.p_naglowek, 0, 0) layout1 = QGridLayout() layout1.addWidget(self.p_dane, 0, 0, 1, 5) layout1.addWidget(self.dodaj, 1, 0) layout1.addWidget(self.usun, 1, 1) layout1.addWidget(self.podziel, 1, 2) layout1.addWidget(self.polacz, 1, 3) layout1.addWidget(self.przerysuj, 1, 4) layout1.setRowMinimumHeight(1, 25) layout1.setRowStretch(0, 1) layout.addLayout(layout1, 0, 1) layout.addLayout(self.vbl, 1, 0, 1, 2) layout.addWidget(self.anuluj, 2, 0) layout.addWidget(self.wykonaj, 2, 1) layout.setColumnMinimumWidth(0, 290) layout.setColumnStretch(1, 1) layout.setRowStretch(0, 1) layout.setRowMinimumHeight(0, 350) self.setLayout(layout) # Sygnaly self.connect(self.anuluj, SIGNAL("clicked()"), self.schowaj) self.connect(self.wykonaj, SIGNAL("clicked()"), self.wykonaj_odczytanie) self.connect(self.dodaj, SIGNAL("clicked()"), self.dodaj_wartosc) self.connect(self.przerysuj, SIGNAL("clicked()"), self.przerysuj_wykres) self.connect(self.usun, SIGNAL("clicked()"), self.usun_wartosc) self.connect(self.podziel, SIGNAL("clicked()"), self.podziel_wartosc) self.connect(self.polacz, SIGNAL("clicked()"), self.polacz_wartosc) self.p_naglowek.cellChanged.connect(self.edytowana_kom_nagl) self.p_dane.itemSelectionChanged.connect(self.przerysuj_wykres) self.p_dane.cellChanged.connect(self.przerysuj_wykres) def prepareDefPol(self): """Prepare Headers for metadata, BEAWARE! measurements are delete on the end""" self.headers = [ 'KeyCode', 'DateBegin', 'DateEnd', 'Length', 'Gat', 'SapWoodRings', 'measurements', ] add_table = [] if len(self.defpol_order): add_table = self.defpol_order[:] elif len(self.defpol.keys()): add_table = sorted(list(self.defpol.keys())) else: add_table = sorted(self.sample.unikalneNaglowki()) for val in add_table: if val not in self.headers: self.headers.append(val) self.headers.remove('measurements') def updateHeaderTable(self): # Dodaj wartosci wierszow dla tabeli naglowka proby self.p_naglowek.blockSignals(True) for i, val in enumerate(self.headers): if self.sample.wypiszMetadana(val): komorka = QTableWidgetItem(str(self.sample.wypiszMetadana(val))) else: komorka = QTableWidgetItem('---') if val == 'Length': komorka.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.p_naglowek.setItem(i, 0, komorka) self.p_naglowek.blockSignals(False) def wykonaj_odczytanie(self): # spisz cala probe z edytowanych tabel i zapisz ja do zmiennej globalnej # daneW self.odczytaj_dane(origin='org') self.newName = self.org_sample.KeyCode() self.hide() def przerysuj_wykres(self): self.odczytaj_dane() self.updateHeaderTable() self.chartData = [ [], # X axis - years [], # Y axis - measurements ] self.chartData[1] = self.sample.wypiszPomiary() self.chartData[0] = range(1, self.sample.Length() + 1) self.qmc.axes.clear() # draw sample curve self.qmc.axes.plot(self.chartData[0], self.chartData[1]) # draw selected years Xpoints = [] Ypoints = [] self.selected = [] j = 0 for i in range(self.sample.Length()): if i == 11: j += 1 item = self.p_dane.item(j, i-(j*10)) if item.isSelected(): Xpoints.append(i+1) self.selected.append(i) Ypoints.append(int(item.text())) self.qmc.axes.plot(Xpoints, Ypoints, 'ro') # formatting of support lines self.qmc.axes.xaxis.set_major_locator(MultipleLocator(10)) self.qmc.axes.xaxis.set_minor_locator(MultipleLocator(2)) # Prepare axis dimensions Xmax = 40 if self.sample.Length() > 40: Xmax = self.sample.Length() + 2 Ymax = max(self.sample.wypiszPomiary()) + 30 self.qmc.axes.axis([0, Xmax, 0, Ymax]) self.qmc.axes.xaxis.grid( True, 'minor', linewidth=0.4, ls='-', color='0.20') self.qmc.axes.xaxis.grid( True, 'major', linewidth=1, ls='-', color='0.80') self.qmc.axes.tick_params(axis='both', which='major', labelsize=10) self.qmc.axes.set_axisbelow(True) # ustawienie obszaru wykresu self.qmc.axes.set_position([0.03, 0.05, 0.96, 0.94]) self.qmc.draw() def odczytaj_dane(self, origin='copy'): """update sample - read all metadata and measurements which could be altered by user. If origin of sample has to be modified use any string in origin """ if origin == 'copy': target = self.sample else: target = self.org_sample for i, head in enumerate(self.headers): ins = unicode(self.p_naglowek.item(i, 0).text()) if head not in ['Length'] and ins not in ['---', 0]: target.wpiszMetadana( head, ins ) rowNum = self.p_dane.rowCount() colNum = self.p_dane.columnCount() measurements = [] check = 'ok' redraw = 0 for w in range(rowNum): for k in range(colNum): item = self.p_dane.item(w, k) try: if int(item.text()) != 0 and str(item.text()).isdigit: measurements.append(int(item.text())) # if after notOk we find number there is something wrong # and we neef to reread measuremnts to tabel if check == 'notOk': redraw = 1 except: check = 'notOk' target.uaktualnijPom(measurements) if redraw == 1: self.wpisz_pomiary() def dodaj_wartosc(self): kk = 0 self.ile_wierszy = self.p_dane.rowCount() self.ile_kol = self.p_dane.columnCount() for wiersz in range(self.ile_wierszy): for kolumna in range(self.ile_kol): item = self.p_dane.item(wiersz, kolumna) if self.p_dane.isItemSelected(item) == True and kk == 0: kk = 1 text, ok = QInputDialog.getText( self, 'Value', 'inser value: ') if ok: measurements = self.sample.wypiszPomiary() measurements.insert((wiersz*10)+kolumna, int(text)) self.sample.uaktualnijPom(measurements) self.wpisz_pomiary() self.przerysuj_wykres() def usun_wartosc(self): sel = self.selected[:] sel.reverse() for s in sel: self.sample.usunOstatniPomiar(position=int(s)) self.wpisz_pomiary() self.przerysuj_wykres() def wpisz_pomiary(self): # przygotowanie labelek poziomych dla tablicy z pomiarami self.w = (len(self.sample.wypiszPomiary()))/10 if (len(self.sample.wypiszPomiary())) % 10 > 0: self.w += 1 i = 0 self.ww = [] while i < self.w: self.ww.append(str(i*10)) i += 1 self.p_dane.blockSignals(True) self.p_dane.clear() self.p_dane.setRowCount(self.w) self.p_dane.setVerticalHeaderLabels(self.ww) self.p_dane.setAlternatingRowColors(True) self.p_dane.setSortingEnabled(False) # Dodajemy wartosci pomiarow i = 0 j = 0 k = 0 measurements = self.sample.wypiszPomiary() while k < len(measurements): if j == 10: j = 0 i += 1 komorka = QTableWidgetItem(str(measurements[k])) self.p_dane.setItem(i, j, komorka) j += 1 k += 1 # uzupelniamy pozostale pola pustymi wartosciami while j % 10 != 0: komorka = QTableWidgetItem("") self.p_dane.setItem(i, j, komorka) j += 1 self.p_dane.resizeColumnsToContents() self.p_dane.blockSignals(False) def podziel_wartosc(self): if len(self.selected) != 1: pass else: measurements = self.sample.wypiszPomiary() val = measurements[self.selected[0]] self.divideWindow = okno_podzialu(val) self.divideWindow.exec_() measurements.pop(self.selected[0]) self.sample.uaktualnijPom(measurements) self.sample.dodajPomiar(self.divideWindow.val1, position=self.selected[0]) self.sample.dodajPomiar(self.divideWindow.val0, position=self.selected[0]) self.wpisz_pomiary() self.przerysuj_wykres() def polacz_wartosc(self): measurements = self.sample.wypiszPomiary() i = len(measurements) - 1 selectionRange = [] newMeasurements = [] while i > -1: if i in self.selected: selectionRange.append(measurements[i]) elif i not in self.selected: if len(selectionRange) > 0: newMeasurements.append(sum(selectionRange)) selectionRange = [] newMeasurements.append(measurements[i]) i -= 1 if len(selectionRange) > 0: newMeasurements.append(selectionRange) newMeasurements.reverse() self.sample.uaktualnijPom(newMeasurements) self.wpisz_pomiary() self.przerysuj_wykres() def schowaj(self): self.odczytaj_dane() self.hide() def edytowana_kom_nagl(self): # neccessary to maintain user specific date, otherwise it will be # shuflled row = self.p_naglowek.currentRow() self.p_naglowek.blockSignals(True) if self.headers[row] in ['DateBegin', 'DateEnd']: head_temp = self.headers[row] if head_temp == "DateBegin": self.sample.ustawDateBegin( int(self.p_naglowek.item(row, 0).text())) it = QTableWidgetItem(str(self.sample.DateEnd())) self.p_naglowek.setItem(self.headers.index("DateEnd"), 0, it) if head_temp == "DateEnd": self.sample.ustawDateEnd( int(self.p_naglowek.item(row, 0).text())) it = QTableWidgetItem(str(self.sample.DateBegin())) self.p_naglowek.setItem(self.headers.index("DateBegin"), 0, it) self.p_naglowek.blockSignals(False) self.przerysuj_wykres()
class SpreadSheet(QMainWindow): dateFormats = ["dd/M/yyyy", "yyyy/M/dd", "dd.MM.yyyy"] currentDateFormat = dateFormats[0] def __init__(self, rows, cols, parent = None): super(SpreadSheet, self).__init__(parent) self.toolBar = QToolBar() self.addToolBar(self.toolBar) self.formulaInput = QLineEdit() self.cellLabel = QLabel(self.toolBar) self.cellLabel.setMinimumSize(80, 0) self.toolBar.addWidget(self.cellLabel) self.toolBar.addWidget(self.formulaInput) self.table = QTableWidget(rows, cols, self) for c in range(cols): character = chr(ord('A') + c) self.table.setHorizontalHeaderItem(c, QTableWidgetItem(character)) self.table.setItemPrototype(self.table.item(rows - 1, cols - 1)) self.table.setItemDelegate(SpreadSheetDelegate(self)) self.createActions() self.updateColor(0) self.setupMenuBar() self.setupContents() self.setupContextMenu() self.setCentralWidget(self.table) self.statusBar() self.table.currentItemChanged.connect(self.updateStatus) self.table.currentItemChanged.connect(self.updateColor) self.table.currentItemChanged.connect(self.updateLineEdit) self.table.itemChanged.connect(self.updateStatus) self.formulaInput.returnPressed.connect(self.returnPressed) self.table.itemChanged.connect(self.updateLineEdit) self.setWindowTitle("Spreadsheet") def createActions(self): self.cell_sumAction = QAction("Sum", self) self.cell_sumAction.triggered.connect(self.actionSum) self.cell_addAction = QAction("&Add", self) self.cell_addAction.setShortcut(Qt.CTRL | Qt.Key_Plus) self.cell_addAction.triggered.connect(self.actionAdd) self.cell_subAction = QAction("&Subtract", self) self.cell_subAction.setShortcut(Qt.CTRL | Qt.Key_Minus) self.cell_subAction.triggered.connect(self.actionSubtract) self.cell_mulAction = QAction("&Multiply", self) self.cell_mulAction.setShortcut(Qt.CTRL | Qt.Key_multiply) self.cell_mulAction.triggered.connect(self.actionMultiply) self.cell_divAction = QAction("&Divide", self) self.cell_divAction.setShortcut(Qt.CTRL | Qt.Key_division) self.cell_divAction.triggered.connect(self.actionDivide) self.fontAction = QAction("Font...", self) self.fontAction.setShortcut(Qt.CTRL | Qt.Key_F) self.fontAction.triggered.connect(self.selectFont) self.colorAction = QAction(QIcon(QPixmap(16, 16)), "Background &Color...", self) self.colorAction.triggered.connect(self.selectColor) self.clearAction = QAction("Clear", self) self.clearAction.setShortcut(Qt.Key_Delete) self.clearAction.triggered.connect(self.clear) self.aboutSpreadSheet = QAction("About Spreadsheet", self) self.aboutSpreadSheet.triggered.connect(self.showAbout) self.exitAction = QAction("E&xit", self) self.exitAction.setShortcut(QKeySequence.Quit) self.exitAction.triggered.connect(QApplication.instance().quit) self.printAction = QAction("&Print", self) self.printAction.setShortcut(QKeySequence.Print) self.printAction.triggered.connect(self.print_) self.firstSeparator = QAction(self) self.firstSeparator.setSeparator(True) self.secondSeparator = QAction(self) self.secondSeparator.setSeparator(True) def setupMenuBar(self): self.fileMenu = self.menuBar().addMenu("&File") self.dateFormatMenu = self.fileMenu.addMenu("&Date format") self.dateFormatGroup = QActionGroup(self) for f in self.dateFormats: action = QAction(f, self, checkable=True, triggered=self.changeDateFormat) self.dateFormatGroup.addAction(action) self.dateFormatMenu.addAction(action) if f == self.currentDateFormat: action.setChecked(True) self.fileMenu.addAction(self.printAction) self.fileMenu.addAction(self.exitAction) self.cellMenu = self.menuBar().addMenu("&Cell") self.cellMenu.addAction(self.cell_addAction) self.cellMenu.addAction(self.cell_subAction) self.cellMenu.addAction(self.cell_mulAction) self.cellMenu.addAction(self.cell_divAction) self.cellMenu.addAction(self.cell_sumAction) self.cellMenu.addSeparator() self.cellMenu.addAction(self.colorAction) self.cellMenu.addAction(self.fontAction) self.menuBar().addSeparator() self.aboutMenu = self.menuBar().addMenu("&Help") self.aboutMenu.addAction(self.aboutSpreadSheet) def changeDateFormat(self): action = self.sender() oldFormat = self.currentDateFormat newFormat = self.currentDateFormat = action.text() for row in range(self.table.rowCount()): item = self.table.item(row, 1) date = QDate.fromString(item.text(), oldFormat) item.setText(date.toString(newFormat)) def updateStatus(self, item): if item and item == self.table.currentItem(): self.statusBar().showMessage(item.data(Qt.StatusTipRole).toString(), 1000) self.cellLabel.setText("Cell: (%s)" % encode_pos(self.table.row(item), self.table.column(item))) def updateColor(self, item): pixmap = QPixmap(16, 16) color = QColor() if item: color = item.backgroundColor() if not color.isValid(): color = self.palette().base().color() painter = QPainter(pixmap) painter.fillRect(0, 0, 16, 16, color) lighter = color.lighter() painter.setPen(lighter) # light frame painter.drawPolyline(QPoint(0, 15), QPoint(0, 0), QPoint(15, 0)) painter.setPen(color.darker()) # dark frame painter.drawPolyline(QPoint(1, 15), QPoint(15, 15), QPoint(15, 1)) painter.end() self.colorAction.setIcon(QIcon(pixmap)) def updateLineEdit(self, item): if item != self.table.currentItem(): return if item: self.formulaInput.setText(item.data(Qt.EditRole).toString()) else: self.formulaInput.clear() def returnPressed(self): text = self.formulaInput.text() row = self.table.currentRow() col = self.table.currentColumn() item = self.table.item(row, col) if not item: self.table.setItem(row, col, SpreadSheetItem(text)) else: item.setData(Qt.EditRole, text) self.table.viewport().update() def selectColor(self): item = self.table.currentItem() color = item and QColor(item.background()) or self.table.palette().base().color() color = QColorDialog.getColor(color, self) if not color.isValid(): return selected = self.table.selectedItems() if not selected: return for i in selected: i and i.setBackground(color) self.updateColor(self.table.currentItem()) def selectFont(self): selected = self.table.selectedItems() if not selected: return font, ok = QFontDialog.getFont(self.font(), self) if not ok: return for i in selected: i and i.setFont(font) def runInputDialog(self, title, c1Text, c2Text, opText, outText, cell1, cell2, outCell): rows = [] cols = [] for r in range(self.table.rowCount()): rows.append(str(r + 1)) for c in range(self.table.columnCount()): cols.append(chr(ord('A') + c)) addDialog = QDialog(self) addDialog.setWindowTitle(title) group = QGroupBox(title, addDialog) group.setMinimumSize(250, 100) cell1Label = QLabel(c1Text, group) cell1RowInput = QComboBox(group) c1Row, c1Col = decode_pos(cell1) cell1RowInput.addItems(rows) cell1RowInput.setCurrentIndex(c1Row) cell1ColInput = QComboBox(group) cell1ColInput.addItems(cols) cell1ColInput.setCurrentIndex(c1Col) operatorLabel = QLabel(opText, group) operatorLabel.setAlignment(Qt.AlignHCenter) cell2Label = QLabel(c2Text, group) cell2RowInput = QComboBox(group) c2Row, c2Col = decode_pos(cell2) cell2RowInput.addItems(rows) cell2RowInput.setCurrentIndex(c2Row) cell2ColInput = QComboBox(group) cell2ColInput.addItems(cols) cell2ColInput.setCurrentIndex(c2Col) equalsLabel = QLabel("=", group) equalsLabel.setAlignment(Qt.AlignHCenter) outLabel = QLabel(outText, group) outRowInput = QComboBox(group) outRow, outCol = decode_pos(outCell) outRowInput.addItems(rows) outRowInput.setCurrentIndex(outRow) outColInput = QComboBox(group) outColInput.addItems(cols) outColInput.setCurrentIndex(outCol) cancelButton = QPushButton("Cancel", addDialog) cancelButton.clicked.connect(addDialog.reject) okButton = QPushButton("OK", addDialog) okButton.setDefault(True) okButton.clicked.connect(addDialog.accept) buttonsLayout = QHBoxLayout() buttonsLayout.addStretch(1) buttonsLayout.addWidget(okButton) buttonsLayout.addSpacing(10) buttonsLayout.addWidget(cancelButton) dialogLayout = QVBoxLayout(addDialog) dialogLayout.addWidget(group) dialogLayout.addStretch(1) dialogLayout.addItem(buttonsLayout) cell1Layout = QHBoxLayout() cell1Layout.addWidget(cell1Label) cell1Layout.addSpacing(10) cell1Layout.addWidget(cell1ColInput) cell1Layout.addSpacing(10) cell1Layout.addWidget(cell1RowInput) cell2Layout = QHBoxLayout() cell2Layout.addWidget(cell2Label) cell2Layout.addSpacing(10) cell2Layout.addWidget(cell2ColInput) cell2Layout.addSpacing(10) cell2Layout.addWidget(cell2RowInput) outLayout = QHBoxLayout() outLayout.addWidget(outLabel) outLayout.addSpacing(10) outLayout.addWidget(outColInput) outLayout.addSpacing(10) outLayout.addWidget(outRowInput) vLayout = QVBoxLayout(group) vLayout.addItem(cell1Layout) vLayout.addWidget(operatorLabel) vLayout.addItem(cell2Layout) vLayout.addWidget(equalsLabel) vLayout.addStretch(1) vLayout.addItem(outLayout) if addDialog.exec_(): cell1 = cell1ColInput.currentText() + cell1RowInput.currentText() cell2 = cell2ColInput.currentText() + cell2RowInput.currentText() outCell = outColInput.currentText() + outRowInput.currentText() return True, cell1, cell2, outCell return False, None, None, None def actionSum(self): row_first = 0 row_last = 0 row_cur = 0 col_first = 0 col_last = 0 col_cur = 0 selected = self.table.selectedItems() if selected: first = selected[0] last = selected[-1] row_first = self.table.row(first) row_last = self.table.row(last) col_first = self.table.column(first) col_last = self.table.column(last) current = self.table.currentItem() if current: row_cur = self.table.row(current) col_cur = self.table.column(current) cell1 = encode_pos(row_first, col_first) cell2 = encode_pos(row_last, col_last) out = encode_pos(row_cur, col_cur) ok, cell1, cell2, out = self.runInputDialog("Sum cells", "First cell:", "Last cell:", u"\N{GREEK CAPITAL LETTER SIGMA}", "Output to:", cell1, cell2, out) if ok: row, col = decode_pos(out) self.table.item(row, col).setText("sum %s %s" % (cell1, cell2)) def actionMath_helper(self, title, op): cell1 = "C1" cell2 = "C2" out = "C3" current = self.table.currentItem() if current: out = encode_pos(self.table.currentRow(), self.table.currentColumn()) ok, cell1, cell2, out = self.runInputDialog(title, "Cell 1", "Cell 2", op, "Output to:", cell1, cell2, out) if ok: row, col = decode_pos(out) self.table.item(row, col).setText("%s %s %s" % (op, cell1, cell2)) def actionAdd(self): self.actionMath_helper("Addition", "+") def actionSubtract(self): self.actionMath_helper("Subtraction", "-") def actionMultiply(self): self.actionMath_helper("Multiplication", "*") def actionDivide(self): self.actionMath_helper("Division", "/") def clear(self): for i in self.table.selectedItems(): i.setText("") def setupContextMenu(self): self.addAction(self.cell_addAction) self.addAction(self.cell_subAction) self.addAction(self.cell_mulAction) self.addAction(self.cell_divAction) self.addAction(self.cell_sumAction) self.addAction(self.firstSeparator) self.addAction(self.colorAction) self.addAction(self.fontAction) self.addAction(self.secondSeparator) self.addAction(self.clearAction) self.setContextMenuPolicy(Qt.ActionsContextMenu) def setupContents(self): titleBackground = QColor(Qt.lightGray) titleFont = self.table.font() titleFont.setBold(True) # column 0 self.table.setItem(0, 0, SpreadSheetItem("Item")) self.table.item(0, 0).setBackground(titleBackground) self.table.item(0, 0).setToolTip("This column shows the purchased item/service") self.table.item(0, 0).setFont(titleFont) self.table.setItem(1, 0, SpreadSheetItem("AirportBus")) self.table.setItem(2, 0, SpreadSheetItem("Flight (Munich)")) self.table.setItem(3, 0, SpreadSheetItem("Lunch")) self.table.setItem(4, 0, SpreadSheetItem("Flight (LA)")) self.table.setItem(5, 0, SpreadSheetItem("Taxi")) self.table.setItem(6, 0, SpreadSheetItem("Dinner")) self.table.setItem(7, 0, SpreadSheetItem("Hotel")) self.table.setItem(8, 0, SpreadSheetItem("Flight (Oslo)")) self.table.setItem(9, 0, SpreadSheetItem("Total:")) self.table.item(9, 0).setFont(titleFont) self.table.item(9, 0).setBackground(Qt.lightGray) # column 1 self.table.setItem(0, 1, SpreadSheetItem("Date")) self.table.item(0, 1).setBackground(titleBackground) self.table.item(0, 1).setToolTip("This column shows the purchase date, double click to change") self.table.item(0, 1).setFont(titleFont) self.table.setItem(1, 1, SpreadSheetItem("15/6/2006")) self.table.setItem(2, 1, SpreadSheetItem("15/6/2006")) self.table.setItem(3, 1, SpreadSheetItem("15/6/2006")) self.table.setItem(4, 1, SpreadSheetItem("21/5/2006")) self.table.setItem(5, 1, SpreadSheetItem("16/6/2006")) self.table.setItem(6, 1, SpreadSheetItem("16/6/2006")) self.table.setItem(7, 1, SpreadSheetItem("16/6/2006")) self.table.setItem(8, 1, SpreadSheetItem("18/6/2006")) self.table.setItem(9, 1, SpreadSheetItem()) self.table.item(9, 1).setBackground(Qt.lightGray) # column 2 self.table.setItem(0, 2, SpreadSheetItem("Price")) self.table.item(0, 2).setBackground(titleBackground) self.table.item(0, 2).setToolTip("This column shows the price of the purchase") self.table.item(0, 2).setFont(titleFont) self.table.setItem(1, 2, SpreadSheetItem("150")) self.table.setItem(2, 2, SpreadSheetItem("2350")) self.table.setItem(3, 2, SpreadSheetItem("-14")) self.table.setItem(4, 2, SpreadSheetItem("980")) self.table.setItem(5, 2, SpreadSheetItem("5")) self.table.setItem(6, 2, SpreadSheetItem("120")) self.table.setItem(7, 2, SpreadSheetItem("300")) self.table.setItem(8, 2, SpreadSheetItem("1240")) self.table.setItem(9, 2, SpreadSheetItem()) self.table.item(9, 2).setBackground(Qt.lightGray) # column 3 self.table.setItem(0, 3, SpreadSheetItem("Currency")) self.table.item(0, 3).setBackgroundColor(titleBackground) self.table.item(0, 3).setToolTip("This column shows the currency") self.table.item(0, 3).setFont(titleFont) self.table.setItem(1, 3, SpreadSheetItem("NOK")) self.table.setItem(2, 3, SpreadSheetItem("NOK")) self.table.setItem(3, 3, SpreadSheetItem("EUR")) self.table.setItem(4, 3, SpreadSheetItem("EUR")) self.table.setItem(5, 3, SpreadSheetItem("USD")) self.table.setItem(6, 3, SpreadSheetItem("USD")) self.table.setItem(7, 3, SpreadSheetItem("USD")) self.table.setItem(8, 3, SpreadSheetItem("USD")) self.table.setItem(9, 3, SpreadSheetItem()) self.table.item(9,3).setBackground(Qt.lightGray) # column 4 self.table.setItem(0, 4, SpreadSheetItem("Ex. Rate")) self.table.item(0, 4).setBackground(titleBackground) self.table.item(0, 4).setToolTip("This column shows the exchange rate to NOK") self.table.item(0, 4).setFont(titleFont) self.table.setItem(1, 4, SpreadSheetItem("1")) self.table.setItem(2, 4, SpreadSheetItem("1")) self.table.setItem(3, 4, SpreadSheetItem("8")) self.table.setItem(4, 4, SpreadSheetItem("8")) self.table.setItem(5, 4, SpreadSheetItem("7")) self.table.setItem(6, 4, SpreadSheetItem("7")) self.table.setItem(7, 4, SpreadSheetItem("7")) self.table.setItem(8, 4, SpreadSheetItem("7")) self.table.setItem(9, 4, SpreadSheetItem()) self.table.item(9,4).setBackground(Qt.lightGray) # column 5 self.table.setItem(0, 5, SpreadSheetItem("NOK")) self.table.item(0, 5).setBackground(titleBackground) self.table.item(0, 5).setToolTip("This column shows the expenses in NOK") self.table.item(0, 5).setFont(titleFont) self.table.setItem(1, 5, SpreadSheetItem("* C2 E2")) self.table.setItem(2, 5, SpreadSheetItem("* C3 E3")) self.table.setItem(3, 5, SpreadSheetItem("* C4 E4")) self.table.setItem(4, 5, SpreadSheetItem("* C5 E5")) self.table.setItem(5, 5, SpreadSheetItem("* C6 E6")) self.table.setItem(6, 5, SpreadSheetItem("* C7 E7")) self.table.setItem(7, 5, SpreadSheetItem("* C8 E8")) self.table.setItem(8, 5, SpreadSheetItem("* C9 E9")) self.table.setItem(9, 5, SpreadSheetItem("sum F2 F9")) self.table.item(9,5).setBackground(Qt.lightGray) def showAbout(self): QMessageBox.about(self, "About Spreadsheet", """ <HTML> <p><b>This demo shows use of <c>QTableWidget</c> with custom handling for individual cells.</b></p> <p>Using a customized table item we make it possible to have dynamic output in different cells. The content that is implemented for this particular demo is: <ul> <li>Adding two cells.</li> <li>Subtracting one cell from another.</li> <li>Multiplying two cells.</li> <li>Dividing one cell with another.</li> <li>Summing the contents of an arbitrary number of cells.</li> </HTML> """) def print_(self): pass
class multirun_widget(QWidget): """Widget for editing multirun values. Keyword arguments: tr -- a translate instance that contains the experimental sequence nrows -- number of rows = number of multirun steps. ncols -- number of columns = number of channels to change in one step. order -- the order to produce the variables list in: ascending - with repeats next to each other descending - with repeats next to each other random - completely randomise the order coarse random - randomise order but repeats next to each other unsorted - make an ascending list, then repeat the list """ multirun_vals = pyqtSignal(np.ndarray) # the array of multirun values progress = pyqtSignal(str) # string detailing the progress of the multirun def __init__(self, tr, nrows=10, ncols=3, order='ascending'): super().__init__() self.tr = tr # translator for the current sequence self.mrtr = tr.copy() # translator for multirun sequence self.msglist = [] # list of multirun sequences as XML string self.ind = 0 # index for how far through the multirun we are self.nrows = nrows self.ncols = ncols self.types = OrderedDict([('measure', int), ('measure_prefix', str), ('1st hist ID', int), ('Variable label', str), ('Order', str), ('Type', strlist), ('Analogue type', strlist), ('Time step name', listlist), ('Analogue channel', listlist), ('runs included', listlist), ('Last time step run', str), ('Last time step end', str), ('# omitted', int), ('# in hist', int)]) self.ui_param = OrderedDict([ ('measure', 0), ('measure_prefix', 'Measure0'), ('1st hist ID', 0), ('Variable label', ''), ('Order', order), ('Type', ['Time step length'] * ncols), ('Analogue type', ['Fast analogue'] * ncols), ('Time step name', [[]] * ncols), ('Analogue channel', [[]] * ncols), ('runs included', [[] for i in range(nrows)]), ('Last time step run', r'C:\Users\lab\Desktop\DExTer 1.4\Last Timesteps\feb2020_940and812.evt' ), ('Last time step end', r'C:\Users\lab\Desktop\DExTer 1.4\Last Timesteps\feb2020_940and812.evt' ), ('# omitted', 0), ('# in hist', 100) ]) self.mr_param = copy.deepcopy( self.ui_param) # parameters used for current multirun self.mr_vals = [] # multirun values for the current multirun self.mr_queue = [ ] # list of parameters, sequences, and values to queue up for future multiruns self.init_UI() # make the widgets def make_label_edit(self, label_text, layout, position=[0, 0, 1, 1], default_text='', validator=None): """Make a QLabel with an accompanying QLineEdit and add them to the given layout with an input validator. The position argument should be [row number, column number, row width, column width].""" label = QLabel(label_text, self) layout.addWidget(label, *position) line_edit = QLineEdit(self) if np.size(position) == 4: position[1] += 1 layout.addWidget(line_edit, *position) line_edit.setText(default_text) line_edit.setValidator(validator) return label, line_edit def init_UI(self): """Create all of the widget objects required""" layout = QVBoxLayout() self.setLayout(layout) # place scroll bars if the contents of the window are too large scroll = QScrollArea(self) layout.addWidget(scroll) scroll_content = QWidget(scroll) scroll.setWidgetResizable(True) scroll.setFixedHeight(800) self.grid = QGridLayout() scroll_content.setLayout(self.grid) #### validators for user input #### double_validator = QDoubleValidator() # floats int_validator = QIntValidator(0, 10000000) # positive integers msr_validator = QIntValidator(-1, 1000000) # integers >= -1 nat_validator = QIntValidator(1, 10000000) # natural numbers col_validator = QIntValidator(1, self.ncols - 1) # for number of columns #### table dimensions and ordering #### # choose the number of rows = number of multirun steps labels = ['# Omit', '# in Histogram', '# Columns', '# Rows'] default = ['0', '100', str(self.ncols), str(self.nrows)] vldtr = [int_validator, nat_validator, nat_validator, nat_validator] self.omit_edit, self.nhist_edit, self.cols_edit, self.rows_edit = [ self.make_label_edit(labels[i], self.grid, [0, 2 * i, 1, 1], default[i], vldtr[i])[1] for i in range(4) ] self.cols_edit.textChanged[str].connect(self.change_array_size) self.rows_edit.textChanged[str].connect(self.change_array_size) self.omit_edit.editingFinished.connect(self.update_repeats) self.nhist_edit.editingFinished.connect(self.update_repeats) # choose the order self.order_edit = QComboBox(self) self.order_edit.addItems( ['ascending', 'descending', 'random', 'coarse random', 'unsorted']) self.grid.addWidget(self.order_edit, 0, 8, 1, 1) #### create multirun list of values #### # metadata for the multirun list: which channels and timesteps self.measures = OrderedDict() labels = ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] defaults = ['Variable 0', '0', 'Measure0', '0'] for i in range(len(labels)): label = QLabel(labels[i], self) self.grid.addWidget(label, i + 1, 0, 1, 1) self.measures[labels[i]] = QLineEdit(defaults[i], self) self.measures[labels[i]].textChanged.connect(self.update_all_stats) self.grid.addWidget(self.measures[labels[i]], i + 1, 1, 1, 3) self.measures['measure'].setValidator(int_validator) self.measures['1st hist ID'].setValidator(msr_validator) label.setText('1st ID (-1 to append)') # change label self.chan_choices = OrderedDict() labels = [ 'Type', 'Time step name', 'Analogue type', 'Analogue channel' ] sht = self.tr.seq_dic['Experimental sequence cluster in'][ 'Sequence header top'] options = [ ['Time step length', 'Analogue voltage', 'GPIB'], list( map(str.__add__, [str(i) for i in range(len(sht))], [ ': ' + hc['Time step name'] if hc['Time step name'] else ': ' for hc in sht ])), ['Fast analogue', 'Slow analogue'], self.get_anlg_chans('Fast') ] positions = [[1, 4, 3, 2], [1, 6, 6, 1], [1, 7, 3, 1], [1, 8, 6, 1]] widgets = [QComboBox, QListWidget] for i in range(0, len(labels)): self.chan_choices[labels[i]] = widgets[i % 2]() if i % 2: self.chan_choices[labels[i]].setSelectionMode(3) self.chan_choices[labels[i]].addItems(options[i]) self.grid.addWidget(self.chan_choices[labels[i]], *positions[i]) self.chan_choices['Type'].currentTextChanged[str].connect( self.change_mr_type) self.chan_choices['Analogue type'].currentTextChanged[str].connect( self.change_mr_anlg_type) self.chan_choices['Analogue channel'].setEnabled(False) # add a new list of multirun values to the array self.col_index = self.make_label_edit('column index:', self.grid, position=[5, 0, 1, 1], default_text='0', validator=col_validator)[1] self.col_range = QLineEdit('linspace(0,1,%s)' % (self.nrows + 1), self) self.grid.addWidget(self.col_range, 5, 2, 1, 2) # show the previously selected channels for this column: self.chan_choices['Time step name'].itemClicked.connect( self.save_chan_selection) self.chan_choices['Analogue channel'].itemClicked.connect( self.save_chan_selection) self.chan_choices['Type'].activated[str].connect( self.save_chan_selection) self.chan_choices['Analogue type'].activated[str].connect( self.save_chan_selection) self.col_index.textChanged[str].connect(self.set_chan_listbox) # add the column to the multirun values array add_var_button = QPushButton('Add column', self) add_var_button.clicked.connect(self.add_column_to_array) add_var_button.resize(add_var_button.sizeHint()) self.grid.addWidget(add_var_button, 6, 0, 1, 1) # clear the current list of user variables clear_vars_button = QPushButton('Clear', self) clear_vars_button.clicked.connect(self.clear_array) clear_vars_button.resize(clear_vars_button.sizeHint()) self.grid.addWidget(clear_vars_button, 6, 1, 1, 1) # suggest new measure when multirun started self.suggest_button = QPushButton('Auto-increment measure', self, checkable=True, checked=True) self.suggest_button.resize(self.suggest_button.sizeHint()) self.grid.addWidget(self.suggest_button, 6, 2, 1, 2) # choose last time step for multirun lts_label = QLabel('Last time step: ', self) self.grid.addWidget(lts_label, 7, 0, 1, 1) self.last_step_run_edit = self.make_label_edit('Running: ', self.grid, position=[7, 1, 1, 3])[1] self.last_step_run_edit.setText(self.ui_param['Last time step run']) self.last_step_run_edit.textChanged[str].connect(self.update_last_step) self.last_step_end_edit = self.make_label_edit('End: ', self.grid, position=[7, 5, 1, 3])[1] self.last_step_end_edit.setText(self.ui_param['Last time step end']) self.last_step_end_edit.textChanged[str].connect(self.update_last_step) # display current progress multirun_progress = QLabel( 'User variable: , omit 0 of 0 files, 0 of 100 histogram files, 0% complete' ) self.grid.addWidget(multirun_progress, 8, 0, 1, 12) remove_slot(self.progress, multirun_progress.setText, True) # table stores multirun values: self.table = QTableWidget(self.nrows, self.ncols) self.reset_array() self.grid.addWidget(self.table, 9, 0, 20, 12) scroll.setWidget(scroll_content) #### #### array editing functions #### #### def reset_array(self, newvals=None): """Empty the table of its values. If newvals are supplied then it should have the right shape (rows, cols) so that it can be used to fill the table items.""" self.table.setHorizontalHeaderLabels(list(map(str, range(self.ncols)))) if not newvals: newvals = [[''] * self.ncols] * self.nrows for i in range(self.table.rowCount()): for j in range(self.ncols): self.table.setItem(i, j, QTableWidgetItem()) self.table.item(i, j).setText(newvals[i][j]) def clear_array(self): """Empty the table of its values and reset the selected channels.""" self.reset_array() self.ui_param['Type'] = ['Time step length'] * self.ncols self.ui_param['Analogue type'] = ['Fast analogue'] * self.ncols self.ui_param['Time step name'] = [[]] * self.ncols self.ui_param['Analogue channel'] = [[]] * self.ncols self.set_chan_listbox(0) def check_table(self): """Check that there are values in each of the cells of the array.""" return all( self.table.item(i, j).text() for i in range(self.table.rowCount()) for j in range(self.table.columnCount())) def get_table(self): """Return a list of all the values in the multirun array table""" return [[ self.table.item(i, j).text() for j in range(self.table.columnCount()) ] for i in range(self.table.rowCount())] def change_array_size(self): """Update the size of the multirun array based on the number of rows and columns specified in the line edit.""" self.nrows = int(self.rows_edit.text()) if self.rows_edit.text() else 1 self.table.setRowCount(self.nrows) self.ncols = int(self.cols_edit.text()) if self.cols_edit.text() else 1 self.table.setColumnCount(self.ncols) self.col_index.setValidator(QIntValidator(1, self.ncols - 1)) if self.col_index.text() and int( self.col_index.text()) > self.ncols - 1: self.col_index.setText(str(self.ncols - 1)) self.reset_array() self.ui_param['runs included'] = [[] for i in range(self.nrows)] for key, default in zip( ['Type', 'Analogue type', 'Time step name', 'Analogue channel'], ['Time step length', 'Fast analogue', [], []]): if len(self.ui_param[key] ) < self.ncols: # these lists must be reshaped self.ui_param[key].append(default) elif len(self.ui_param[key]) > self.ncols: self.ui_param[key] = self.ui_param[key][:self.ncols] def update_all_stats(self, toggle=False): """Shorthand to update the values of the stats dictionary from the text labels.""" self.update_repeats() self.update_last_step() for key in self.measures.keys( ): # ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] if self.measures[key].text( ): # don't do anything if the line edit is empty try: self.ui_param[key] = self.types[key]( self.measures[key].text()) except: pass # probably while user was typing the '-' in '-1' def update_repeats(self, txt=''): """Take the current values of the line edits and use them to set the number of omitted and number of included runs in a histogram.""" self.ui_param['# omitted'] = int( self.omit_edit.text()) if self.omit_edit.text() else 0 self.ui_param['# in hist'] = int( self.nhist_edit.text()) if self.nhist_edit.text() else 1 def update_last_step(self, txt=''): """Save the current values of the last time step file paths.""" self.ui_param['Last time step run'] = self.last_step_run_edit.text() self.ui_param['Last time step end'] = self.last_step_end_edit.text() def add_column_to_array(self): """Make a list of values and add it to the given column in the multirun values array. The function is chosen by the user. Values are repeated a set number of times, ordered according to the ComboBox text. The selected channels are stored in lists.""" if 'linspace' in self.col_range.text( ): # choose the generating function f = np.linspace elif 'logspace' in self.col_range.text(): f = np.logspace elif 'range' in self.col_range.text(): f = np.arange else: return 0 try: # make the list of values vals = f(*map( float, self.col_range.text().split('(')[-1].replace(')', '').split( ','))) except (ZeroDivisionError, TypeError, ValueError) as e: logger.warning('Add column to multirun: invalid syntax "' + self.col_range.text() + '".\n' + str(e)) return 0 col = int(self.col_index.text()) if self.col_index.text() else 0 # store the selected channels self.ui_param['Order'] = self.order_edit.currentText() for key in self.measures.keys( ): # ['Variable label', 'measure', 'measure_prefix', '1st hist ID'] if self.measures[key].text( ): # don't do anything if the line edit is empty self.ui_param[key] = self.types[key](self.measures[key].text()) # order the list of values if self.ui_param['Order'] == 'descending': vals = list(reversed(vals)) elif 'random' in self.ui_param['Order']: vals = list(vals) shuffle(vals) for i in range(self.table.rowCount()): try: # set vals in table cells self.table.item(i, col).setText('%.5g' % vals[i]) except IndexError: # occurs if invalid range self.table.item(i, col).setText('') #### multirun channel selection #### def reset_sequence(self, tr): """Update the translator object used to get the experimental sequence. This is used to set the labels for time step names and channel names. Note: the multirun sequence mrtr is not affected.""" self.tr = tr sht = self.tr.seq_dic['Experimental sequence cluster in'][ 'Sequence header top'] for key, items in [[ 'Time step name', list( map(str.__add__, [str(i) for i in range(len(sht))], [ ': ' + hc['Time step name'] if hc['Time step name'] else ': ' for hc in sht ])) ], ['Analogue channel', self.get_anlg_chans('Fast')]]: self.chan_choices[key].clear() self.chan_choices[key].addItems(items) # note: selected channels might have changed order self.set_chan_listbox(self.col_index.text()) def save_chan_selection(self, arg=None): """When the user changes the selection of channels/timesteps for the given column, save it. The selection will be reloaded if the user changes the column and then comes back.""" col = int(self.col_index.text()) if self.col_index.text() else 0 for key in ['Type', 'Analogue type']: self.ui_param[key][col] = self.chan_choices[key].currentText() for key in ['Time step name', 'Analogue channel']: self.ui_param[key][col] = list( map(self.chan_choices[key].row, self.chan_choices[key].selectedItems())) def set_chan_listbox(self, col): """Set the selected channels and timesteps with the values previously stored for the given column col. If there were no values stored previously or the index is out of range, reset the selection.""" try: col = int(col) if col else 0 mrtype = self.ui_param['Type'][col] antype = self.ui_param['Analogue type'][col] sel = { 'Time step name': self.ui_param['Time step name'][col], 'Analogue channel': self.ui_param['Analogue channel'][col] if mrtype == 'Analogue voltage' else [] } except (IndexError, ValueError): mrtype, antype = 'Time step length', 'Fast analogue' sel = {'Time step name': [], 'Analogue channel': []} self.chan_choices['Type'].setCurrentText(mrtype) self.chan_choices['Analogue type'].setCurrentText(antype) self.chan_choices['Analogue channel'].setEnabled( True if mrtype == 'Analogue voltage' else False) for key in ['Time step name', 'Analogue channel']: self.chan_choices[key].setCurrentRow( 0, QItemSelectionModel.Clear) # clear previous selection try: for i in sel[key]: # select items at the stored indices self.chan_choices[key].item(i).setSelected(True) except IndexError: pass # perhaps sequence was updated but using old selection indices except AttributeError as e: logger.warning( "Couldn't set channels for the loaded multirun parameters. Load the sequence first, then load multirun parameters.\n" + str(e)) def get_anlg_chans(self, speed): """Return a list of name labels for the analogue channels. speed -- 'Fast' or 'Slow'""" d = self.tr.seq_dic['Experimental sequence cluster in'][ speed + ' analogue names'] return map(str.__add__, d['Hardware ID'], [': ' + name if name else '' for name in d['Name']]) def change_mr_type(self, newtype): """Enable/Disable list boxes to reflect the multirun type: newtype[str] -- Time step length: only needs timesteps -- Analogue voltage: also needs channels""" if newtype == 'Time step length': self.chan_choices['Analogue channel'].setEnabled(False) elif newtype == 'Analogue voltage': self.chan_choices['Analogue channel'].setEnabled(True) self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems( self.get_anlg_chans( self.chan_choices['Analogue type'].currentText().split( ' ')[0])) def change_mr_anlg_type(self, newtype): """Change the analogue channels listbox when fast/slow analogue channels are selected.""" if self.chan_choices['Analogue channel'].isEnabled(): self.chan_choices['Analogue channel'].clear() self.chan_choices['Analogue channel'].addItems( self.get_anlg_chans( self.chan_choices['Analogue type'].currentText().split( ' ')[0])) def get_next_index(self, rn): """Choose the next index from the rows of the table to use in the multirun, based on the order chosen. rn: the ID of the current run within the multirun. make rn modulo nrows so that there isn't an index error on the last run.""" # if self.mr_param['Order'] == 'unsorted': # return rn % self.nrows # elif self.mr_param['Order'] == 'random': # return randint(0, self.nrows - 1) # else: # if descending, ascending, or coarse random, the order has already been set return (rn // (self.mr_param['# omitted'] + self.mr_param['# in hist']) ) % len(self.mr_param['runs included'] ) # ID of histogram in repetition cycle def get_next_sequence(self, i=None, save_dir=''): """Use the values in the multirun array to make the next sequence to run in the multirun. Uses saved mr_param not UI""" if i == None: i = self.ind # row index esc = self.mrtr.seq_dic[ 'Experimental sequence cluster in'] # shorthand try: for col in range(len(self.mr_vals[i])): # edit the sequence val = float(self.mr_vals[i][col]) if self.mr_param['Type'][col] == 'Time step length': for head in [ 'Sequence header top', 'Sequence header middle' ]: for t in self.mr_param['Time step name'][col]: esc[head][t]['Time step length'] = val elif self.mr_param['Type'][col] == 'Analogue voltage': for t in self.mr_param['Time step name'][col]: for c in self.mr_param['Analogue channel'][col]: esc[self.mr_param['Analogue type'][col] + ' array'][c]['Voltage'][t] = val self.mrtr.seq_dic['Routine name in'] = 'Multirun ' + self.mr_param['Variable label'] + \ ': ' + self.mr_vals[i][0] + ' (%s / %s)'%(i+1, len(self.mr_vals)) if save_dir: self.mrtr.write_to_file( os.path.join( save_dir, self.mr_param['measure_prefix'] + '_' + str(i + self.mr_param['1st hist ID']) + '.xml')) except IndexError as e: logger.error('Multirun failed to edit sequence at ' + self.mr_param['Variable label'] + ' = ' + self.mr_vals[i][0] + '\n' + str(e)) return self.mrtr.write_to_str() def get_all_sequences(self, save_dir=''): """Use the multirun array vals to make all of the sequences that will be used in the multirun, then store these as a list of XML strings.""" self.msglist = [] for i in range(len(self.mr_vals)): self.msglist.append(self.get_next_sequence(i, save_dir)) #### save and load parameters #### def view_mr_queue(self): """Pop up message box displays the queued multiruns""" text = 'Would you like to clear the following list of queued multiruns?\n' for params, _, _, _ in self.mr_queue: text += params['measure_prefix'] + '\t' + params[ 'Variable label'] + '\n' reply = QMessageBox.question(self, 'Queued Multiruns', text, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.mr_queue = [] def try_browse(self, title='Select a File', file_type='all (*)', open_func=QFileDialog.getOpenFileName): """Open a file dialog and retrieve a file name from the browser. title: String to display at the top of the file browser window file_type: types of files that can be selected open_func: the function to use to open the file browser""" try: if 'PyQt4' in sys.modules: file_name = open_func(self, title, '', file_type) elif 'PyQt5' in sys.modules: file_name, _ = open_func(self, title, '', file_type) return file_name except OSError: return '' # probably user cancelled def save_mr_params(self, save_file_name=''): """Save the variable label, measure, measure prefix, # runs omitted, # runs per histogram, multirun type, list of timesteps, multirun # analogue type, list of channels, and array of variables.""" if not save_file_name: save_file_name = self.try_browse( title='Save File', file_type='csv(*.csv);;all (*)', open_func=QFileDialog.getSaveFileName) if save_file_name: if hasattr(self.sender(), 'text') and self.sender().text() == 'Save Parameters': params, vals = self.ui_param, self.get_table() # save from UI else: params, vals = self.mr_param, self.mr_vals # save from multirun with open(save_file_name, 'w+') as f: f.write('Multirun list of variables:\n') f.write(';'.join([ ','.join([vals[row][col] for col in range(len(vals[0]))]) for row in range(len(vals)) ]) + '\n') f.write(';'.join(params.keys()) + '\n') f.write(';'.join(map(str, list(params.values())))) def load_mr_params(self, load_file_name=''): """Load the multirun variables array from a file.""" if not load_file_name: load_file_name = self.try_browse( title='Load File', file_type='csv(*.csv);;all (*)', open_func=QFileDialog.getOpenFileName) if load_file_name: with open(load_file_name, 'r') as f: _ = f.readline() vals = [ x.split(',') for x in f.readline().replace('\n', '').split(';') ] header = f.readline().replace('\n', '').split(';') params = f.readline().split(';') for i in range(len(header)): if header[i] in self.ui_param: try: self.ui_param[header[i]] = self.types[header[i]]( params[i]) except ValueError as e: logger.error( 'Multirun editor could not load parameter: %s\n' % params[i] + str(e)) # store values in case they're overwritten after setText() nrows, ncols = np.shape(vals) # update array of values col = int(self.col_index.text()) if self.col_index.text() else 0 nhist, nomit = map( str, [self.ui_param['# in hist'], self.ui_param['# omitted']]) runstep, endstep = self.ui_param[ 'Last time step run'], self.ui_param['Last time step end'] # then update the label edits for key in self.measures.keys( ): # update variable label and measure remove_slot(self.measures[key].textChanged, self.update_all_stats, False) self.measures[key].setText(str(self.ui_param[key])) remove_slot(self.measures[key].textChanged, self.update_all_stats, True) self.set_chan_listbox(col if col < ncols else 0) self.rows_edit.setText(str(nrows)) # triggers change_array_size self.cols_edit.setText(str(ncols)) self.change_array_size() # don't wait for it to be triggered self.reset_array(vals) self.nhist_edit.setText(nhist) self.omit_edit.setText(nomit) self.last_step_run_edit.setText( runstep) # triggers update_last_step self.last_step_end_edit.setText(endstep) def check_mr_params(self, save_results_path='.'): """Check that the multirun parameters are valid before adding it to the queue""" results_path = os.path.join(save_results_path, self.ui_param['measure_prefix']) appending = False # first check if the measure folder already exists with some files in imax = 0 try: filelist = os.listdir(results_path) for fname in filelist: if 'params' in fname: try: # look for multirun parameters file with open(os.path.join(results_path, fname), 'r') as f: _ = f.readline() vals = f.readline().replace('\n', '').split(';') header = f.readline().replace('\n', '').split(';') params = f.readline().split(';') imax = max( imax, len(vals) + int(params[header.index('1st hist ID')]) - 1) except: pass except FileNotFoundError: pass # then check the multirun queue for m in self.mr_queue: if self.ui_param['measure_prefix'] == m[0]['measure_prefix']: imax = max(imax, len(m[2]) + m[0]['1st hist ID'] - 1) if self.ui_param['1st hist ID'] == -1: # append at the end appending = True self.ui_param['1st hist ID'] = imax + 1 if imax > 0 else 0 if (os.path.isdir(results_path) or self.ui_param['measure_prefix'] in [ x[0]['measure_prefix'] for x in self.mr_queue ]) and imax >= self.ui_param['1st hist ID']: # this measure exists, check if user wants to overwrite reply = QMessageBox.question( self, 'Confirm Overwrite', "Results path already exists, do you want to overwrite the files?\n" + results_path, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.No: return 0 # parameters are valid, add to queue self.mr_queue.append([ copy.deepcopy(self.ui_param), self.tr.copy(), self.get_table(), appending ]) if self.suggest_button.isChecked( ): # suggest new multirun measure ID and prefix n = self.ui_param['measure'] + 1 self.measures['measure'].setText(str(n)) self.measures['measure_prefix'].setText('Measure' + str(n)) return 1
class Main(plugin.Plugin): " Main Class " def initialize(self, *args, **kwargs): " Init Main Class " super(Main, self).initialize(*args, **kwargs) self.scriptPath, self.scriptArgs = "", [] self.profilerPath, self.tempPath = profilerPath, tempPath self.output = " ERROR: FAIL: No output ! " self.process = QProcess() self.process.finished.connect(self.on_process_finished) self.process.error.connect(self.on_process_error) self.tabWidget, self.stat = QTabWidget(), QWidget() self.tabWidget.tabCloseRequested.connect(lambda: self.tabWidget.setTabPosition(1) if self.tabWidget.tabPosition() == 0 else self.tabWidget.setTabPosition(0)) self.tabWidget.setStyleSheet('QTabBar{font-weight:bold;}') self.tabWidget.setMovable(True) self.tabWidget.setTabsClosable(True) self.vboxlayout1 = QVBoxLayout(self.stat) self.hboxlayout1 = QHBoxLayout() self.filterTableLabel = QLabel("<b>Type to Search : </b>", self.stat) self.hboxlayout1.addWidget(self.filterTableLabel) self.filterTableLineEdit = QLineEdit(self.stat) self.filterTableLineEdit.setPlaceholderText(' Type to Search . . . ') self.hboxlayout1.addWidget(self.filterTableLineEdit) self.filterHintTableLabel = QLabel(" ? ", self.stat) self.hboxlayout1.addWidget(self.filterHintTableLabel) self.vboxlayout1.addLayout(self.hboxlayout1) self.tableWidget = QTableWidget(self.stat) self.tableWidget.setAlternatingRowColors(True) self.tableWidget.setColumnCount(8) self.tableWidget.setRowCount(2) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(0, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(1, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(2, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(3, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(4, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(5, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(6, item) item = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(7, item) self.tableWidget.itemDoubleClicked.connect( self.on_tableWidget_itemDoubleClicked) self.vboxlayout1.addWidget(self.tableWidget) self.tabWidget.addTab(self.stat, " ? ") self.source = QWidget() self.gridlayout = QGridLayout(self.source) self.scintillaWarningLabel = QLabel( "QScintilla is not installed!. Falling back to basic text edit!.", self.source) self.gridlayout.addWidget(self.scintillaWarningLabel, 1, 0, 1, 2) self.sourceTreeWidget = QTreeWidget(self.source) self.sourceTreeWidget.setAlternatingRowColors(True) self.sourceTreeWidget.itemActivated.connect( self.on_sourceTreeWidget_itemActivated) self.sourceTreeWidget.itemClicked.connect( self.on_sourceTreeWidget_itemClicked) self.sourceTreeWidget.itemDoubleClicked.connect( self.on_sourceTreeWidget_itemClicked) self.gridlayout.addWidget(self.sourceTreeWidget, 0, 0, 1, 1) self.sourceTextEdit = QTextEdit(self.source) self.sourceTextEdit.setReadOnly(True) self.gridlayout.addWidget(self.sourceTextEdit, 0, 1, 1, 1) self.tabWidget.addTab(self.source, " ? ") self.result = QWidget() self.vlayout = QVBoxLayout(self.result) self.globalStatGroupBox = QGroupBox(self.result) self.hboxlayout = QHBoxLayout(self.globalStatGroupBox) self.totalTimeLcdNumber = QLCDNumber(self.globalStatGroupBox) self.totalTimeLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.totalTimeLcdNumber.setNumDigits(7) self.totalTimeLcdNumber.display(1000000) self.totalTimeLcdNumber.setFrameShape(QFrame.StyledPanel) self.totalTimeLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.totalTimeLcdNumber) self.tTimeLabel = QLabel("<b>Total Time (Sec)</b>", self.globalStatGroupBox) self.tTimeLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.tTimeLabel) self.numCallLcdNumber = QLCDNumber(self.globalStatGroupBox) self.numCallLcdNumber.setNumDigits(7) self.numCallLcdNumber.display(1000000) self.numCallLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.numCallLcdNumber.setFrameShape(QFrame.StyledPanel) self.numCallLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.numCallLcdNumber) self.numCallLabel = QLabel("<b>Number of calls</b>", self.globalStatGroupBox) self.numCallLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.numCallLabel) self.primCallLcdNumber = QLCDNumber(self.globalStatGroupBox) self.primCallLcdNumber.setSegmentStyle(QLCDNumber.Filled) self.primCallLcdNumber.setFrameShape(QFrame.StyledPanel) self.primCallLcdNumber.setNumDigits(7) self.primCallLcdNumber.display(1000000) self.primCallLcdNumber.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hboxlayout.addWidget(self.primCallLcdNumber) self.primCallLabel = QLabel("<b>Primitive calls (%)</b>", self.globalStatGroupBox) self.primCallLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hboxlayout.addWidget(self.primCallLabel) self.vlayout.addWidget(self.globalStatGroupBox) try: from PyKDE4.kdeui import KRatingWidget self.rating = KRatingWidget(self.globalStatGroupBox) self.rating.setToolTip('Profiling Performance Rating') except ImportError: pass self.tabWidget.addTab(self.result, " Get Results ! ") self.resgraph = QWidget() self.vlayout2 = QVBoxLayout(self.result) self.graphz = QGroupBox(self.resgraph) self.hboxlayout2 = QHBoxLayout(self.graphz) try: from PyKDE4.kdeui import KLed KLed(self.graphz) except ImportError: pass self.hboxlayout2.addWidget(QLabel(''' Work in Progress :) Not Ready Yet''')) self.vlayout2.addWidget(self.graphz) self.tabWidget.addTab(self.resgraph, " Graphs and Charts ") self.pathz = QWidget() self.vlayout3 = QVBoxLayout(self.pathz) self.patz = QGroupBox(self.pathz) self.hboxlayout3 = QVBoxLayout(self.patz) self.profilepath = QLineEdit(profilerPath) self.getprofile = QPushButton(QIcon.fromTheme("document-open"), 'Open') self.getprofile.setToolTip('Dont touch if you dont know what are doing') self.getprofile.clicked.connect(lambda: self.profilepath.setText(str( QFileDialog.getOpenFileName(self.patz, ' Open the profile.py file ', path.expanduser("~"), ';;(profile.py)')))) self.hboxlayout3.addWidget(QLabel( '<center><b>Profile.py Python Library Full Path:</b></center>')) self.hboxlayout3.addWidget(self.profilepath) self.hboxlayout3.addWidget(self.getprofile) self.argGroupBox = QGroupBox(self.pathz) self.hbxlayout = QHBoxLayout(self.argGroupBox) self.argLineEdit = QLineEdit(self.argGroupBox) self.argLineEdit.setToolTip('Not touch if you dont know what are doing') self.argLineEdit.setPlaceholderText( 'Dont touch if you dont know what are doing') self.hbxlayout.addWidget(QLabel('<b>Additional Profile Arguments:</b>')) self.hbxlayout.addWidget(self.argLineEdit) self.hboxlayout3.addWidget(self.argGroupBox) self.vlayout3.addWidget(self.patz) self.tabWidget.addTab(self.pathz, " Paths and Configs ") self.outp = QWidget() self.vlayout4 = QVBoxLayout(self.outp) self.outgro = QGroupBox(self.outp) self.outgro.setTitle(" MultiProcessing Output Logs ") self.hboxlayout4 = QVBoxLayout(self.outgro) self.outputlog = QTextEdit() self.outputlog.setText(''' I do not fear computers, I fear the lack of them. -Isaac Asimov ''') self.hboxlayout4.addWidget(self.outputlog) self.vlayout4.addWidget(self.outgro) self.tabWidget.addTab(self.outp, " Logs ") self.actionNew_profiling = QAction(QIcon.fromTheme("document-new"), 'New Profiling', self) self.actionLoad_profile = QAction(QIcon.fromTheme("document-open"), 'Open Profiling', self) self.actionClean = QAction(QIcon.fromTheme("edit-clear"), 'Clean', self) self.actionClean.triggered.connect(lambda: self.clearContent) self.actionAbout = QAction(QIcon.fromTheme("help-about"), 'About', self) self.actionAbout.triggered.connect(lambda: QMessageBox.about(self.dock, __doc__, ', '.join((__doc__, __license__, __author__, __email__)))) self.actionSave_profile = QAction(QIcon.fromTheme("document-save"), 'Save Profiling', self) self.actionManual = QAction(QIcon.fromTheme("help-contents"), 'Help', self) self.actionManual.triggered.connect(lambda: open_new_tab('http://docs.python.org/library/profile.html')) self.tabWidget.setCurrentIndex(2) self.globalStatGroupBox.setTitle("Global Statistics") item = self.tableWidget.horizontalHeaderItem(0) item.setText("Number of Calls") item = self.tableWidget.horizontalHeaderItem(1) item.setText("Total Time") item = self.tableWidget.horizontalHeaderItem(2) item.setText("Per Call") item = self.tableWidget.horizontalHeaderItem(3) item.setText("Cumulative Time") item = self.tableWidget.horizontalHeaderItem(4) item.setText("Per Call") item = self.tableWidget.horizontalHeaderItem(5) item.setText("Filename") item = self.tableWidget.horizontalHeaderItem(6) item.setText("Line") item = self.tableWidget.horizontalHeaderItem(7) item.setText("Function") self.tabWidget.setTabText(self.tabWidget.indexOf(self.stat), "Statistics per Function") self.sourceTreeWidget.headerItem().setText(0, "Source files") self.tabWidget.setTabText(self.tabWidget.indexOf(self.source), "Sources Navigator") ####################################################################### self.scrollable, self.dock = QScrollArea(), QDockWidget() self.scrollable.setWidgetResizable(True) self.scrollable.setWidget(self.tabWidget) self.dock.setWindowTitle(__doc__) self.dock.setStyleSheet('QDockWidget::title{text-align: center;}') self.dock.setWidget(self.scrollable) QToolBar(self.dock).addActions((self.actionNew_profiling, self.actionClean, self.actionSave_profile, self.actionLoad_profile, self.actionManual, self.actionAbout)) self.actionNew_profiling.triggered.connect( self.on_actionNew_profiling_triggered) self.actionLoad_profile.triggered.connect( self.on_actionLoad_profile_triggered) self.actionSave_profile.triggered.connect( self.on_actionSave_profile_triggered) self.locator.get_service('misc').add_widget(self.dock, QIcon.fromTheme("document-open-recent"), __doc__) if QSCI: # Scintilla source editor management self.scintillaWarningLabel.setText(' QScintilla is Ready ! ') layout = self.source.layout() layout.removeWidget(self.sourceTextEdit) self.sourceTextEdit = Qsci.QsciScintilla(self.source) layout.addWidget(self.sourceTextEdit, 0, 1) doc = self.sourceTextEdit doc.setLexer(Qsci.QsciLexerPython(self.sourceTextEdit)) doc.setReadOnly(True) doc.setEdgeMode(Qsci.QsciScintilla.EdgeLine) doc.setEdgeColumn(80) doc.setEdgeColor(QColor("#FF0000")) doc.setFolding(Qsci.QsciScintilla.BoxedTreeFoldStyle) doc.setBraceMatching(Qsci.QsciScintilla.SloppyBraceMatch) doc.setCaretLineVisible(True) doc.setMarginLineNumbers(1, True) doc.setMarginWidth(1, 25) doc.setTabWidth(4) doc.setEolMode(Qsci.QsciScintilla.EolUnix) self.marker = {} for color in COLORS: mnr = doc.markerDefine(Qsci.QsciScintilla.Background) doc.setMarkerBackgroundColor(color, mnr) self.marker[color] = mnr self.currentSourcePath = None # Connect table and tree filter edit signal to unique slot self.filterTableLineEdit.textEdited.connect( self.on_filterLineEdit_textEdited) # Timer to display filter hint message self.filterHintTimer = QTimer(self) self.filterHintTimer.setSingleShot(True) self.filterHintTimer.timeout.connect(self.on_filterHintTimer_timeout) # Timer to start search self.filterSearchTimer = QTimer(self) self.filterSearchTimer.setSingleShot(True) self.filterSearchTimer.timeout.connect( self.on_filterSearchTimer_timeout) self.tabLoaded = {} for i in range(10): self.tabLoaded[i] = False self.backgroundTreeMatchedItems = {} self.resizeWidgetToContent(self.tableWidget) def on_actionNew_profiling_triggered(self): self.clearContent() self.scriptPath = str(QFileDialog.getOpenFileName(self.dock, "Choose your script to profile", path.expanduser("~"), "Python (*.py *.pyw)")) commandLine = [self.profilerPath, "-o", self.tempPath, self.scriptPath] + self.scriptArgs commandLine = " ".join(commandLine) ##if self.termCheckBox.checkState() == Qt.Checked: #termList = ["xterm", "aterm"] #for term in termList: #termPath = which(term) #if termPath: #break #commandLine = """%s -e "%s ; echo 'Press ENTER Exit' ; read" """ \ #% (termPath, commandLine) self.process.start(commandLine) if not self.process.waitForStarted(): print((" ERROR: {} failed!".format(commandLine))) return def on_process_finished(self, exitStatus): ' whan the process end ' print((" INFO: OK: QProcess is %s" % self.process.exitCode())) self.output = self.process.readAll().data() if not self.output: self.output = " ERROR: FAIL: No output ! " self.outputlog.setText(self.output + str(self.process.exitCode())) if path.exists(self.tempPath): self.setStat(self.tempPath) remove(self.tempPath) else: self.outputlog.setText(" ERROR: QProcess FAIL: Profiling failed.") self.tabWidget.setCurrentIndex(2) def on_process_error(self, error): ' when the process fail, I hope you never see this ' print(" ERROR: QProcess FAIL: Profiler Dead, wheres your God now ? ") if error == QProcess.FailedToStart: self.outputlog.setText(" ERROR: FAIL: Profiler execution failed ") elif error == QProcess.Crashed: self.outputlog.setText(" ERROR: FAIL: Profiler execution crashed ") else: self.outputlog.setText(" ERROR: FAIL: Profiler unknown error ") def on_actionLoad_profile_triggered(self): """Load a previous profile sessions""" statPath = str(QFileDialog.getOpenFileName(self.dock, "Open profile dump", path.expanduser("~"), "Profile file (*)")) if statPath: self.clearContent() print(' INFO: OK: Loading profiling from ' + statPath) self.setStat(statPath) def on_actionSave_profile_triggered(self): """Save a profile sessions""" statPath = str(QFileDialog.getSaveFileName(self.dock, "Save profile dump", path.expanduser("~"), "Profile file (*)")) if statPath: #TODO: handle error case and give feelback to user print(' INFO: OK: Saving profiling to ' + statPath) self.stat.save(statPath) #=======================================================================# # Common parts # #=======================================================================# def on_tabWidget_currentChanged(self, index): """slot for tab change""" # Kill search and hint timer if running to avoid cross effect for timer in (self.filterHintTimer, self.filterSearchTimer): if timer.isActive(): timer.stop() if not self.stat: #No stat loaded, nothing to do return self.populateTable() self.populateSource() def on_filterLineEdit_textEdited(self, text): """slot for filter change (table or tree""" if self.filterSearchTimer.isActive(): # Already runnning, stop it self.filterSearchTimer.stop() # Start timer self.filterSearchTimer.start(300) def on_filterHintTimer_timeout(self): """Timeout to warn user about text length""" print("timeout") tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: label = self.filterHintTableLabel label.setText("Type > 2 characters to search") def on_filterSearchTimer_timeout(self): """timeout to start search""" tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: text = self.filterTableLineEdit.text() label = self.filterHintTableLabel edit = self.filterTableLineEdit widget = self.tableWidget else: print("Unknow tab for filterSearch timeout !") print(("do search for %s" % text)) if not len(text): # Empty keyword, just clean all if self.filterHintTimer.isActive(): self.filterHintTimer.stop() label.setText(" ? ") self.warnUSer(True, edit) self.clearSearch() return if len(text) < 2: # Don't filter if text is too short and tell it to user self.filterHintTimer.start(600) return else: if self.filterHintTimer.isActive(): self.filterHintTimer.stop() label.setText(" ? ") # Search self.clearSearch() matchedItems = [] if tab == TAB_FUNCTIONSTAT: # Find items matchedItems = widget.findItems(text, Qt.MatchContains) widget.setSortingEnabled(False) matchedRows = [item.row() for item in matchedItems] # Hide matched items header = widget.verticalHeader() for row in range(widget.rowCount()): if row not in matchedRows: header.hideSection(row) widget.setSortingEnabled(True) else: print(" Unknow tab for filterSearch timeout ! ") print(("got %s members" % len(matchedItems))) self.warnUSer(matchedItems, edit) self.resizeWidgetToContent(widget) def resizeWidgetToContent(self, widget): """Resize all columns according to content""" for i in range(widget.columnCount()): widget.resizeColumnToContents(i) def clearSearch(self): """Clean search result For table, show all items For tree, remove colored items""" tab = self.tabWidget.currentIndex() if tab == TAB_FUNCTIONSTAT: header = self.tableWidget.verticalHeader() if header.hiddenSectionCount(): for i in range(header.count()): if header.isSectionHidden(i): header.showSection(i) def clearContent(self): # Clear tabs self.tableWidget.clearContents() self.sourceTreeWidget.clear() # Reset LCD numbers for lcdNumber in (self.totalTimeLcdNumber, self.numCallLcdNumber, self.primCallLcdNumber): lcdNumber.display(1000000) # Reset stat self.pstat = None # Disable save as menu self.actionSave_profile.setEnabled(False) # Mark all tabs as unloaded for i in range(10): self.tabLoaded[i] = False def warnUSer(self, result, inputWidget): palette = inputWidget.palette() if result: palette.setColor(QPalette.Normal, QPalette.Base, QColor(255, 255, 255)) else: palette.setColor(QPalette.Normal, QPalette.Base, QColor(255, 136, 138)) inputWidget.setPalette(palette) inputWidget.update() def setStat(self, statPath): self.stat = Stat(path=statPath) # Global stat update self.totalTimeLcdNumber.display(self.stat.getTotalTime()) self.numCallLcdNumber.display(self.stat.getCallNumber()) self.primCallLcdNumber.display(self.stat.getPrimitiveCallRatio()) # Refresh current tab self.on_tabWidget_currentChanged(self.tabWidget.currentIndex()) # Activate save as menu self.actionSave_profile.setEnabled(True) try: self.rating.setMaxRating(10) self.rating.setRating( int(self.stat.getPrimitiveCallRatio()) / 10 - 1) except: pass #========================================================================# # Statistics table # #=======================================================================# def populateTable(self): row = 0 rowCount = self.stat.getStatNumber() progress = QProgressDialog("Populating statistics table...", "Abort", 0, 2 * rowCount) self.tableWidget.setSortingEnabled(False) self.tableWidget.setRowCount(rowCount) progress.setWindowModality(Qt.WindowModal) for (key, value) in self.stat.getStatItems(): #ncalls item = StatTableWidgetItem(str(value[0])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_NCALLS, item) colorTableItem(item, self.stat.getCallNumber(), value[0]) #total time item = StatTableWidgetItem(str(value[2])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_TTIME, item) colorTableItem(item, self.stat.getTotalTime(), value[2]) #per call (total time) if value[0] != 0: tPerCall = str(value[2] / value[0]) cPerCall = str(value[3] / value[0]) else: tPerCall = "" cPerCall = "" item = StatTableWidgetItem(tPerCall) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_TPERCALL, item) colorTableItem(item, 100.0 * self.stat.getTotalTime() / self.stat.getCallNumber(), tPerCall) #per call (cumulative time) item = StatTableWidgetItem(cPerCall) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_CPERCALL, item) colorTableItem(item, 100.0 * self.stat.getTotalTime() / self.stat.getCallNumber(), cPerCall) #cumulative time item = StatTableWidgetItem(str(value[3])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_CTIME, item) colorTableItem(item, self.stat.getTotalTime(), value[3]) #Filename self.tableWidget.setItem(row, STAT_FILENAME, StatTableWidgetItem(str(key[0]))) #Line item = StatTableWidgetItem(str(key[1])) item.setTextAlignment(Qt.AlignRight) self.tableWidget.setItem(row, STAT_LINE, item) #Function name self.tableWidget.setItem(row, STAT_FUNCTION, StatTableWidgetItem(str(key[2]))) row += 1 # Store it in stat hash array self.stat.setStatLink(item, key, TAB_FUNCTIONSTAT) progress.setValue(row) if progress.wasCanceled(): return for i in range(self.tableWidget.rowCount()): progress.setValue(row + i) for j in range(self.tableWidget.columnCount()): item = self.tableWidget.item(i, j) if item: item.setFlags(Qt.ItemIsEnabled) self.tableWidget.setSortingEnabled(True) self.resizeWidgetToContent(self.tableWidget) progress.setValue(2 * rowCount) def on_tableWidget_itemDoubleClicked(self, item): matchedItems = [] filename = str(self.tableWidget.item(item.row(), STAT_FILENAME).text()) if not filename or filename.startswith("<"): # No source code associated, return immediatly return function = self.tableWidget.item(item.row(), STAT_FUNCTION).text() line = self.tableWidget.item(item.row(), STAT_LINE).text() self.on_tabWidget_currentChanged(TAB_SOURCE) # load source tab function = "%s (%s)" % (function, line) fathers = self.sourceTreeWidget.findItems(filename, Qt.MatchContains, SOURCE_FILENAME) print(("find %s father" % len(fathers))) for father in fathers: findItems(father, function, SOURCE_FILENAME, matchedItems) print(("find %s items" % len(matchedItems))) if matchedItems: self.tabWidget.setCurrentIndex(TAB_SOURCE) self.sourceTreeWidget.scrollToItem(matchedItems[0]) self.on_sourceTreeWidget_itemClicked(matchedItems[0], SOURCE_FILENAME) matchedItems[0].setSelected(True) else: print("oups, item found but cannot scroll to it !") #=======================================================================# # Source explorer # #=====================================================================# def populateSource(self): items = {} for stat in self.stat.getStatKeys(): source = stat[0] function = "%s (%s)" % (stat[2], stat[1]) if source in ("", "profile") or source.startswith("<"): continue # Create the function child child = QTreeWidgetItem([function]) # Store it in stat hash array self.stat.setStatLink(child, stat, TAB_SOURCE) if source in items: father = items[source] else: # Create the father father = QTreeWidgetItem([source]) items[source] = father father.addChild(child) self.sourceTreeWidget.setSortingEnabled(False) for value in list(items.values()): self.sourceTreeWidget.addTopLevelItem(value) self.sourceTreeWidget.setSortingEnabled(True) def on_sourceTreeWidget_itemActivated(self, item, column): self.on_sourceTreeWidget_itemClicked(item, column) def on_sourceTreeWidget_itemClicked(self, item, column): line = 0 parent = item.parent() if QSCI: doc = self.sourceTextEdit if parent: pathz = parent.text(column) result = match("(.*) \(([0-9]+)\)", item.text(column)) if result: try: function = str(result.group(1)) line = int(result.group(2)) except ValueError: # We got garbage... falling back to line 0 pass else: pathz = item.text(column) pathz = path.abspath(str(pathz)) if self.currentSourcePath != pathz: # Need to load source self.currentSourcePath == pathz try: if QSCI: doc.clear() doc.insert(file(pathz).read()) else: self.sourceTextEdit.setPlainText(file(pathz).read()) except IOError: QMessageBox.warning(self, "Error", "Source file could not be found", QMessageBox.Ok) return if QSCI: for function, line in [(i[2], i[1] ) for i in self.stat.getStatKeys() if i[0] == pathz]: # expr, regexp, case sensitive, whole word, wrap, forward doc.findFirst("def", False, True, True, False, True, line, 0, True) end, foo = doc.getCursorPosition() time = self.stat.getStatTotalTime((pathz, line, function)) colorSource(doc, self.stat.getTotalTime(), time, line, end, self.marker) if QSCI: doc.ensureLineVisible(line)