def write(self, ds_out): if out.version() >= QtCore.QDataStream.Qt_4_4: isCompressed = 1 if self._svgBuffers: svgBuffers = self._svgBuffers else: svgBuffers = {} # QHash<int, QByteArray> for key, v in self._svgFiles.items(): buf = QtCore.QByteArray() f = QtCore.QFile(v) if f.open(QtCore.QIODevice.ReadOnly): buf = f.readAll() buf = QtCore.qCompress(buf) svgBuffers[key] = buf out << self._svgFiles << isCompressed << svgBuffers if self._addedPixmaps: out << 1 << self._addedPixmaps else: out << 0 else: buf = QtCore.QByteArray() if self._svgBuffers: buf = self._svgBuffers[self.default_key] if buf.isEmpty(): svgFile = self._svgFiles[self.default_key] if not svgFile.isEmpty(): f = QtCore.QFile(svgFile) if f.open(QtCore.QIODevice.ReadOnly): buf = f.readAll() buf = QtCore.qCompress(buf) out << buf # 4.3 has buggy handling of added pixmaps, so don't write any out << 0 return True
def handleMessage(self): socket = self._server.nextPendingConnection() if socket.waitForReadyRead(self._timeout): self.messageAvailable.emit( socket.readAll().data().decode('utf-8')) socket.disconnectFromServer() else: QtCore.qDebug(socket.errorString())
def _loadDataForModeAndState(self, renderer, mode, state): """Load SVG data to renderer. """ # First, try to load from a buffer if available. if (mode, state) in self._svgBuffers: buf = self._svgBuffers[(mode, state)] elif self.default_key in self._svgBuffers: buf = self._svgBuffers[self.default_key] else: buf = QtCore.QByteArray() if buf: buf = QtCore.qUncompress(buf) renderer.load(buf) else: # If no buffer is available, load from file if (mode, state) in self._svgFiles: svgFile = self._svgFiles[(mode, state)] renderer.load(self._replace_in_stream(svgFile)) elif self.default_key in self._svgFiles: svgFile = self._svgFiles[self.default_key] if mode == QtGui.QIcon.Disabled: renderer.load(self._replace_in_stream(svgFile, 'disabled')) else: renderer.load(self._replace_in_stream(svgFile))
def read(self, ds_in): self._svgBuffers = {} # QHash<int, QByteArray> if ds_in.version() >= QtCore.QDataStream.Qt_4_4: nfiles = ds_in.readInt() fileNames = {} for i in range(nfiles): fileNames[i] = ds_in.readString() isCompressed = ds_in.readBool() key = ds_in.readInt() self._svgBuffers[key] = QtCore.QByteArray() ds_in >> self._svgBuffers[key] if not isCompressed: for key, v in self._svgBuffers.items(): self._svgBuffers[key] = QtCore.qCompress(v) hasAddedPixmaps = ds_in.readInt() if hasAddedPixmaps: npixmaps = ds_in.readInt() self._addedPixmaps = {} for i in range(npixmaps): pm = QtGui.QPixmap() ds_in >> pm self._addedPixmaps[i] = pm else: pixmap = QtGui.QPixmap() data = QtCore.QByteArray() ds_in >> data if not data.isEmpty(): data = QtCore.qUncompress(data) if not data.isEmpty(): self._svgBuffers[self.default_key] = data num_entries = ds_in.readInt() for i in range(num_entries): if ds_in.atEnd(): return False ds_in >> pixmap mode = ds_in.readUInt32() state = ds_in.readUInt32() # The pm list written by 4.3 is buggy and/or useless, so ignore # self._addPixmap(pixmap, QIcon.Mode(mode), QIcon.State(state)) return True
class LoadView(QtWidgets.QWidget): sig_loading_finished = QtCore.Signal() def __init__(self, parent=None): super(LoadView, self).__init__(parent) self.browse_button = QtWidgets.QPushButton("User Dirs", self) self.browse_button.clicked.connect(self.show_directory_manager) self.co_button = QtWidgets.QPushButton("Co-Add", self) self.load_button = QtWidgets.QPushButton("Load", self) self.spinbox = QtWidgets.QSpinBox(self) self.spinbox.setRange(0, 99999) self.spinbox.setValue(0) self.last_spinbox_val = 0 self.grid = QtWidgets.QVBoxLayout() self.grid.addWidget(self.spinbox) self.grid.addWidget(self.load_button) self.grid.addWidget(self.co_button) self.grid.addWidget(self.browse_button) self.setLayout(self.grid) def show_directory_manager(self): manageuserdirectories.ManageUserDirectories.openManageUserDirectories() def disable_buttons(self): self.spinbox.setEnabled(False) self.load_button.setEnabled(False) self.co_button.setEnabled(False) def enable_buttons(self): self.spinbox.setEnabled(True) self.load_button.setEnabled(True) self.co_button.setEnabled(True) self.sig_loading_finished.emit() def on_load_clicked(self, slot): self.load_button.clicked.connect(slot) def unreg_on_load_clicked(self, slot): try: self.load_button.clicked.disconnect(slot) except TypeError: return def on_co_add_clicked(self, slot): self.co_button.clicked.connect(slot) def unreg_on_co_add_clicked(self, slot): try: self.co_button.clicked.disconnect(slot) except TypeError: return def on_spinbox_changed(self, slot): self.spinbox.valueChanged.connect(slot) def unreg_on_spinbox_changed(self, slot): try: self.spinbox.valueChanged.disconnect(slot) except TypeError: return def on_loading_finished(self, slot): self.sig_loading_finished.connect(slot) def unreg_on_loading_finished(self, slot): try: self.sig_loading_finished.disconnect(slot) except TypeError: return def warning(self, msg): message_box.warning(msg)
try: logf = "crash.log" fh = open(logf, 'a') fh.write(str(msg)+'\n') fh.write('\n'.join(traceback.format_stack())) fh.close() except: print("Failed to write crash log:") traceback.print_exc() if msgType == QtCore.QtFatalMsg: try: sys.exit() QtCore.QCoreApplication.instance().processEvents() except: pass if 'PyQt4' in sys.modules: QtCore.qInstallMsgHandler(messageHandler) else: def qt5_messageHandler(msgType, context, msg): """ Handle error messages from Qt. @param QtMsType msgType: message type @param QMesageLogContrxt context: message context @param str msg: error message from Qt """ messageHandler(msgType, msg) QtCore.qInstallMessageHandler(qt5_messageHandler)
class FFTView(QtWidgets.QWidget): """ creates the layout for the FFT GUI """ # signals buttonSignal = QtCore.Signal() tableClickSignal = QtCore.Signal(object, object) phaseCheckSignal = QtCore.Signal() def __init__(self, parent=None): super(FFTView, self).__init__(parent) self.grid = QtWidgets.QGridLayout(self) # add splitter for resizing splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) # make table self.FFTTable = QtWidgets.QTableWidget(self) self.FFTTable.resize(800, 800) self.FFTTable.setRowCount(6) self.FFTTable.setColumnCount(2) self.FFTTable.setColumnWidth(0, 300) self.FFTTable.setColumnWidth(1, 300) self.FFTTable.verticalHeader().setVisible(False) self.FFTTable.horizontalHeader().setStretchLastSection(True) self.FFTTable.setHorizontalHeaderLabels( ("FFT Property;Value").split(";")) # populate table options = ['test'] table_utils.setRowName(self.FFTTable, 0, "Workspace") self.ws = table_utils.addComboToTable(self.FFTTable, 0, options) self.Im_box_row = 1 table_utils.setRowName(self.FFTTable, self.Im_box_row, "Imaginary Data") self.Im_box = table_utils.addCheckBoxToTable(self.FFTTable, True, self.Im_box_row) table_utils.setRowName(self.FFTTable, 2, "Imaginary Workspace") self.Im_ws = table_utils.addComboToTable(self.FFTTable, 2, options) self.shift_box_row = 3 table_utils.setRowName(self.FFTTable, self.shift_box_row, "Auto shift") self.shift_box = table_utils.addCheckBoxToTable( self.FFTTable, True, self.shift_box_row) table_utils.setRowName(self.FFTTable, 4, "Shift") self.shift = table_utils.addDoubleToTable(self.FFTTable, 0.0, 4) self.FFTTable.hideRow(4) table_utils.setRowName(self.FFTTable, 5, "Use Raw data") self.Raw_box = table_utils.addCheckBoxToTable(self.FFTTable, True, 5) self.FFTTable.resizeRowsToContents() # make advanced table options self.advancedLabel = QtWidgets.QLabel("\n Advanced Options") self.FFTTableA = QtWidgets.QTableWidget(self) self.FFTTableA.resize(800, 800) self.FFTTableA.setRowCount(4) self.FFTTableA.setColumnCount(2) self.FFTTableA.setColumnWidth(0, 300) self.FFTTableA.setColumnWidth(1, 300) self.FFTTableA.verticalHeader().setVisible(False) self.FFTTableA.horizontalHeader().setStretchLastSection(True) self.FFTTableA.setHorizontalHeaderLabels( ("Advanced Property;Value").split(";")) table_utils.setRowName(self.FFTTableA, 0, "Apodization Function") options = ["Lorentz", "Gaussian", "None"] self.apodization = table_utils.addComboToTable(self.FFTTableA, 0, options) table_utils.setRowName(self.FFTTableA, 1, "Decay Constant (micro seconds)") self.decay = table_utils.addDoubleToTable(self.FFTTableA, 4.4, 1) table_utils.setRowName(self.FFTTableA, 2, "Negative Padding") self.negativePadding = table_utils.addCheckBoxToTable( self.FFTTableA, True, 2) table_utils.setRowName(self.FFTTableA, 3, "Padding") self.padding = table_utils.addSpinBoxToTable(self.FFTTableA, 1, 3) self.FFTTableA.resizeRowsToContents() # make button self.button = QtWidgets.QPushButton('Calculate FFT', self) self.button.setStyleSheet("background-color:lightgrey") # connects self.FFTTable.cellClicked.connect(self.tableClick) self.button.clicked.connect(self.buttonClick) self.ws.currentIndexChanged.connect(self.phaseCheck) # add to layout self.FFTTable.setMinimumSize(40, 158) self.FFTTableA.setMinimumSize(40, 127) table_utils.setTableHeaders(self.FFTTable) table_utils.setTableHeaders(self.FFTTableA) # add to layout splitter.addWidget(self.FFTTable) splitter.addWidget(self.advancedLabel) splitter.addWidget(self.FFTTableA) self.grid.addWidget(splitter) self.grid.addWidget(self.button) def getLayout(self): return self.grid def addItems(self, options): self.ws.clear() self.ws.addItems(options) self.Im_ws.clear() self.Im_ws.addItems(options) self.phaseQuadChanged() def removeIm(self, pattern): index = self.Im_ws.findText(pattern) self.Im_ws.removeItem(index) def removeRe(self, pattern): index = self.ws.findText(pattern) self.ws.removeItem(index) # connect signals def phaseCheck(self): self.phaseCheckSignal.emit() def tableClick(self, row, col): self.tableClickSignal.emit(row, col) def buttonClick(self): self.buttonSignal.emit() # responses to commands def activateButton(self): self.button.setEnabled(True) def deactivateButton(self): self.button.setEnabled(False) def setPhaseBox(self): self.FFTTable.setRowHidden(8, "PhaseQuad" not in self.workspace) def changed(self, box, row): self.FFTTable.setRowHidden(row, box.checkState() == QtCore.Qt.Checked) def changedHideUnTick(self, box, row): self.FFTTable.setRowHidden(row, box.checkState() != QtCore.Qt.Checked) def phaseQuadChanged(self): # hide complex ws self.FFTTable.setRowHidden(2, "PhaseQuad" in self.workspace) def set_raw_checkbox_state(self, state): if state: self.Raw_box.setCheckState(QtCore.Qt.Checked) else: self.Raw_box.setCheckState(QtCore.Qt.Unchecked) def setup_raw_checkbox_changed(self, slot): self.FFTTable.itemChanged.connect(self.raw_checkbox_changed) self.signal_raw_option_changed = slot def raw_checkbox_changed(self, table_item): if table_item == self.Raw_box: self.signal_raw_option_changed() def getImBoxRow(self): return self.Im_box_row def getShiftBoxRow(self): return self.shift_box_row def getImBox(self): return self.Im_box def getShiftBox(self): return self.shift_box def warning_popup(self, message): warning(message, parent=self) @property def workspace(self): return str(self.ws.currentText()) @workspace.setter def workspace(self, name): index = self.ws.findText(name) if index == -1: return self.ws.setCurrentIndex(index) @property def imaginary_workspace(self): return str(self.Im_ws.currentText()) @imaginary_workspace.setter def imaginary_workspace(self, name): index = self.Im_ws.findText(name) if index == -1: return self.Im_ws.setCurrentIndex(index) @property def imaginary_data(self): return self.Im_box.checkState() == QtCore.Qt.Checked @imaginary_data.setter def imaginary_data(self, value): if value: self.Im_box.setCheckState(QtCore.Qt.Checked) else: self.Im_box.setCheckState(QtCore.Qt.Unchecked) @property def auto_shift(self): return self.shift_box.checkState() == QtCore.Qt.Checked @property def use_raw_data(self): return self.Raw_box.checkState() == QtCore.Qt.Checked @property def apodization_function(self): return str(self.apodization.currentText()) @property def decay_constant(self): return float(self.decay.text()) @property def negative_padding(self): return self.negativePadding.checkState() == QtCore.Qt.Checked @property def padding_value(self): return int(self.padding.text())
def create_kline_page(self, MainWindow): self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setEnabled(True) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setContentsMargins( -1, -1, -1, 0) # I have no idea how layouts work self.gridLayout.setHorizontalSpacing(0) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.dateEdit_2 = QtWidgets.QDateEdit(self.centralwidget) #End Date sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.dateEdit_2.sizePolicy().hasHeightForWidth()) self.dateEdit_2.setSizePolicy(sizePolicy) self.dateEdit_2.setObjectName(_fromUtf8("dateEdit_2")) self.gridLayout.addWidget(self.dateEdit_2, 4, 1, 1, 1) self.label = QtWidgets.QLabel(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.label.sizePolicy().hasHeightForWidth()) self.label.setSizePolicy(sizePolicy) self.label.setObjectName(_fromUtf8("label")) self.gridLayout.addWidget(self.label, 3, 0, 1, 1) self.dateEdit = QtWidgets.QDateEdit(self.centralwidget) #Start date sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.dateEdit.sizePolicy().hasHeightForWidth()) self.dateEdit.setSizePolicy(sizePolicy) self.dateEdit.setObjectName(_fromUtf8("dateEdit")) self.gridLayout.addWidget(self.dateEdit, 4, 0, 1, 1) self.label_2 = QtWidgets.QLabel(self.centralwidget) #Shows "enddate" sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.label_2.sizePolicy().hasHeightForWidth()) self.label_2.setSizePolicy(sizePolicy) self.label_2.setObjectName(_fromUtf8("label_2")) self.gridLayout.addWidget(self.label_2, 3, 1, 1, 1) self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.treeWidget = QtWidgets.QTreeWidget( self.centralwidget) #Displays stock according to business type sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(2) sizePolicy.setHeightForWidth( self.treeWidget.sizePolicy().hasHeightForWidth()) self.treeWidget.setSizePolicy(sizePolicy) self.treeWidget.setObjectName(_fromUtf8("treeWidget")) self.treeWidget.headerItem().setText(0, _fromUtf8("历史数据")) self.verticalLayout_3.addWidget(self.treeWidget) self.gridLayout.addLayout(self.verticalLayout_3, 1, 0, 1, 3) self.commandLinkButton = QtWidgets.QCommandLinkButton( self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.commandLinkButton.sizePolicy().hasHeightForWidth()) self.commandLinkButton.setSizePolicy(sizePolicy) self.commandLinkButton.setText(_fromUtf8("")) self.commandLinkButton.setObjectName(_fromUtf8("commandLinkButton")) self.gridLayout.addWidget(self.commandLinkButton, 4, 2, 1, 1) self.comboBox = QtWidgets.QComboBox( self.centralwidget) #Combobox for Selecting type of graph sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.comboBox.sizePolicy().hasHeightForWidth()) self.comboBox.setSizePolicy(sizePolicy) self.comboBox.setObjectName(_fromUtf8("comboBox")) self.gridLayout.addWidget(self.comboBox, 3, 2, 1, 1) self.treeWidget_2 = QtWidgets.QTreeWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.treeWidget_2.sizePolicy().hasHeightForWidth()) self.treeWidget_2.setSizePolicy( sizePolicy) #Shows what graphs are selected self.treeWidget_2.setObjectName(_fromUtf8("treeWidget_2")) self.treeWidget_2.headerItem().setText(0, _fromUtf8("绘图项")) self.gridLayout.addWidget(self.treeWidget_2, 5, 0, 1, 3) self.gridLayout.setColumnStretch(0, 60) self.gridLayout.setColumnStretch(1, 20) self.gridLayout.setColumnStretch(2, 20) self.horizontalLayout.addLayout(self.gridLayout) self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.widget = QtWidgets.QWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.widget.sizePolicy().hasHeightForWidth()) self.widget.setSizePolicy(sizePolicy) self.widget.setObjectName(_fromUtf8("widget")) self.widget = QWebEngineView( ) #This is for displaying html content generated by pyecharts self.verticalLayout.addWidget(self.widget) self.horizontalLayout.addLayout(self.verticalLayout) self.horizontalLayout.setStretch(0, 1) self.horizontalLayout.setStretch(1, 15) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QToolBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 900, 23)) self.menubar.setObjectName(_fromUtf8("menubar")) #self.menubar.setNativeMenuBar(False) self.combobox = QtWidgets.QComboBox() self.menubar.addWidget(self.combobox) self.combobox.addItems(["K线", "复权", "分笔数据", "历史分钟", "十大股东"]) self.comboBox.setFixedSize(55, 40) MainWindow.addToolBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
class ThreadUpdater(QtCore.QObject): """General timer that will call functions on an interval. DEBUG Types: * PRINT_ERROR ["print"]: If an error occurs print the traceback to stderr. * HIDE_ERROR ["hide"]: If an error occurs ignore it and do not display the error. * RAISE_ERROR ["raise"]: If an error occurs actually raise the error. This will crash the updater. Args: timeout (float/int)[1/30]: Interval in which to run the update functions (in seconds). debug_type (str)[PRINT_ERROR]: Control to manage how errors are handled. parent (QtCore.QObject)[None]: Parent QObject. """ starting = QtCore.Signal() # Signal to start the timer in the main thread. stopping = QtCore.Signal() # Signal to stop the timer in the main thread. creating = QtCore.Signal( ) # Signal to create the timer in the main thread. class DebugTypes: PRINT_ERROR = 'print' # Print to stderr HIDE_ERROR = 'hide' # Do nothing and hide the error RAISE_ERROR = 'raise' # Crash the update function and raise the error PRINT_ERROR = DebugTypes.PRINT_ERROR HIDE_ERROR = DebugTypes.HIDE_ERROR RAISE_ERROR = DebugTypes.RAISE_ERROR DEFAULT_DEBUG_TYPE = PRINT_ERROR def __init__(self, timeout=1 / 30, debug_type=None, parent=None, init_later=False, **kwargs): """Initialize the ThreadUpdater. Args: timeout (float/int)[1/30]: Interval in which to run the update functions (in seconds). debug_type (str)[DEFAULT_DEBUG_TYPE]: Control to manage how errors are handled. parent (QtCore.QObject)[None]: Parent QObject. init_later (bool)[False]: Manually initialize this object later with `init()`. """ super().__init__(parent) if debug_type is None: debug_type = self.DEFAULT_DEBUG_TYPE # Lock and update variables self._lock = threading.RLock() self._latest_call = OrderedDict() self._every_call = OrderedDict() self._always_call = OrderedDict() self._delay_call = [] # Control variables self._timeout = timeout self.debug_type = debug_type self._running = False self._tmr = None # Try to initialize later so thread variables can be set as fast as possible. if not init_later: self.init() def init(self, *args, **kwargs): """Initialize here; Try to make creating the object in __init__ as fast as possible and with little complexity. This is to reduce the chance that two threads create the global MAIN_UPDATER at the same time. Yes, I've seen this and it was problematic. """ # Move to main thread before connecting the signals, so signals run in the main thread if not is_main_thread(): self.moveToThread(QtWidgets.QApplication.instance().thread()) # Connect the signals self.starting.connect(self.start) self.stopping.connect(self.stop) self.creating.connect(self.create_timer) # Create the timer self.create_timer() return self @contextlib.contextmanager def handle_error(self, func=None): """Context manager to handle exceptions if the unknown update functions cause an error. Change how the errors are handled with the "debug_type" variable. * PRINT_ERROR ["print"]: If an error occurs print the traceback to stderr. * HIDE_ERROR ["hide"]: If an error occurs ignore it and do not display the error. * RAISE_ERROR ["raise"]: If an error occurs actually raise the error. This will crash the updater. """ if self.debug_type == self.RAISE_ERROR: yield # If this errors it will crash the updater and raise teh real error. elif self.debug_type == self.HIDE_ERROR: try: yield except Exception: pass else: # self.debug_type == self.PRINT_ERROR: try: yield except Exception: traceback.print_exc() print('Error in {}'.format(func.__name__), file=sys.stderr) @contextlib.contextmanager def restart_on_change(self, restart=None): """Context manager to stop and restart the timer if it is running.""" if restart is None: restart = self.is_running() if restart: self.stop() yield self.start() else: yield def get_timeout(self): """Return the update timer interval in seconds.""" return self._timeout def set_timeout(self, value): """Set the update timer interval in seconds.""" with self.restart_on_change(self.is_running()): self._timeout = value try: self._tmr.setInterval(int(self.get_timeout() * 1000)) except (AttributeError, RuntimeError, Exception): pass def create_timer(self): """Actually create the timer.""" # Check to run this function in the main thread. if not is_main_thread(): self.creating.emit() return self.stop(set_state=False) self._tmr = QtCore.QTimer() self._tmr.setSingleShot(False) self._tmr.setInterval(int(self.get_timeout() * 1000)) self._tmr.timeout.connect(self.run_update) def is_running(self): """Return if running.""" return self._running def stop(self, set_state=True): """Stop the updater timer.""" # Check to run this function in the main thread. if not is_main_thread(): self.stopping.emit() return try: self._tmr.stop() except: pass if set_state: self._running = False def start(self): """Start the updater timer.""" # Check to run this function in the main thread. if not is_main_thread(): self.starting.emit() return self.stop(set_state=False) self._running = True if self._tmr is None: self.create_timer() # Should be in main thread self._tmr.start() def ensure_running(self): """If the updater is not running send a safe signal to start it.""" if not self.is_running(): self._running = True self.starting.emit() def register_continuous(self, func, *args, **kwargs): """Register a function to be called on every update continuously.""" with self._lock: self._always_call[func] = (args, kwargs) self.ensure_running() def unregister_continuous(self, func): """Unregister a function to be called on every update continuously.""" with self._lock: try: self._always_call.pop(func, None) except: pass def call_latest(self, func, *args, **kwargs): """Call the most recent values for this function in the main thread on the next update call.""" with self._lock: self._latest_call[func] = (args, kwargs) self.ensure_running() def now_call_latest(self, func, *args, **kwargs): """Call the latest value in the main thread. If this is the main thread call now.""" if is_main_thread(): func(*args, **kwargs) else: self.call_latest(func, *args, **kwargs) def call_in_main(self, func, *args, **kwargs): """Call this function in the main thread on the next update call.""" with self._lock: try: self._every_call[func].append((args, kwargs)) except (KeyError, IndexError, Exception): self._every_call[func] = [(args, kwargs)] self.ensure_running() def now_call_in_main(self, func, *args, **kwargs): """Call in the main thread. If this is the main thread call now.""" if is_main_thread(): func(*args, **kwargs) else: self.call_in_main(func, *args, **kwargs) def delay(self, seconds, func, *args, **kwargs): """Call the given function after the given number of seconds has passed. This will not be accurate unless your timeout is at a high rate (lower timeout number). Args: seconds (float/int): Number of seconds to wait until calling the function. func (callable): Function to call. *args (tuple): Positional arguments to pass into the function. **kwargs (dict): Keyword arguments to pass into the function. """ now = time.time() # Note: this is before the lock with self._lock: self._delay_call.append( DelayedFunc(now, seconds, func, args, kwargs)) self.ensure_running() def run_update(self): """Run the stored function calls to update the GUI items in the main thread. This function should not be called directly. Call `ThreadUpdater.start()` to run this function on a timer in the main thread. """ # Collect the items using the thread safe lock with self._lock: always = self._always_call.copy() latest, self._latest_call = self._latest_call, OrderedDict() main, self._every_call = self._every_call, OrderedDict() delayed = [ self._delay_call.pop(i) for i in reversed(range(len(self._delay_call))) if self._delay_call[i].can_run() ] # Start running the functions for delayed_func in delayed: with self.handle_error(delayed_func.func): delayed_func.func(*delayed_func.args, **delayed_func.kwargs) for func, (args, kwargs) in always.items(): with self.handle_error(func): func(*args, **kwargs) for func, (args, kwargs) in latest.items(): with self.handle_error(func): func(*args, **kwargs) for func, li in main.items(): for args, kwargs in li: with self.handle_error(func): func(*args, **kwargs)
class VnaSelector(QtWidgets.QWidget): enableStateToggled = QtCore.Signal(bool) def __init__(self, parent=None): super(VnaSelector, self).__init__(parent) # --- Setup UI Elements --- # self.verticalLayout = QtWidgets.QVBoxLayout( self) # primary widget layout self.verticalLayout.setContentsMargins( 0, 0, 0, 0) # normally this will be embedded in another application self.checkBox_SweepNew = QtWidgets.QCheckBox("Sweep New", self) self.checkBox_SweepNew.setLayoutDirection(QtCore.Qt.RightToLeft) self.checkBox_RawData = QtWidgets.QCheckBox("Raw Data", self) self.checkBox_RawData.setLayoutDirection(QtCore.Qt.RightToLeft) self.label_ports = QtWidgets.QLabel("ports 1,2:") self.spinBox_port1 = QtWidgets.QSpinBox(self) self.spinBox_port2 = QtWidgets.QSpinBox(self) for port in (self.spinBox_port1, self.spinBox_port2): port.setMinimum(1) port.setMaximum(2) self.spinBox_port1.setValue(1) self.spinBox_port2.setValue(2) self.label_analyzerList = QtWidgets.QLabel("Select Analyzer", self) self.comboBox_analyzer = QtWidgets.QComboBox(self) self.hlay_analyzerList = QtWidgets.QHBoxLayout() self.hlay_analyzerList.addWidget(self.label_analyzerList) self.hlay_analyzerList.addWidget(self.comboBox_analyzer) self.label_visaString = QtWidgets.QLabel("Visa String", self) self.lineEdit_visaString = QtWidgets.QLineEdit(self) self.row1 = QtWidgets.QHBoxLayout() self.row1.addLayout(self.hlay_analyzerList) self.row1.addWidget(self.label_visaString) self.row1.addWidget(self.lineEdit_visaString) self.row1.insertStretch(-1) self.label_channel = QtWidgets.QLabel("Channel:") self.spinBox_channel = QtWidgets.QSpinBox() self.spinBox_channel.setMinimum(1) self.spinBox_channel.setMaximum(256) self.btn_controlVna = QtWidgets.QPushButton("Set VNA State") self.row2 = QtWidgets.QHBoxLayout() self.row2.addWidget(self.label_channel) self.row2.addWidget(self.spinBox_channel) self.row2.addWidget(qt.QVLine()) self.row2.addWidget(self.checkBox_SweepNew) self.row2.addWidget(qt.QVLine()) self.row2.addWidget(self.checkBox_RawData) self.row2.addWidget(qt.QVLine()) self.row2.addWidget(self.label_ports) self.row2.addWidget(self.spinBox_port1) self.row2.addWidget(self.spinBox_port2) self.row2.addWidget(qt.QVLine()) self.row2.addWidget(self.btn_controlVna) self.row2.insertStretch(-1) self.verticalLayout.addLayout(self.row1) self.verticalLayout.addLayout(self.row2) self.comboBox_analyzer.currentIndexChanged.connect( self.update_selected_analyzer) for key, val in analyzers.items(): self.comboBox_analyzer.addItem(key) # --- End Setup UI Elements --- # self.btn_controlVna.clicked.connect(self.control_vna) self.btn_controlVna.setEnabled(False) def setEnabled(self, enabled): super(VnaSelector, self).setEnabled(enabled) self.enableStateToggled.emit(enabled) def update_selected_analyzer(self): cls = analyzers[self.comboBox_analyzer.currentText()] self.lineEdit_visaString.setText(cls.DEFAULT_VISA_ADDRESS) self.spinBox_port2.setMaximum(cls.NPORTS) self.spinBox_channel.setMaximum(cls.NCHANNELS) def get_analyzer(self): nwa = analyzers[self.comboBox_analyzer.currentText()]( self.lineEdit_visaString.text()) nwa.set_measurement_parameters(port1=self.port1, port2=self.port2, sweep=self.sweep_new, channel=self.channel, raw_data=self.raw_data) return nwa @property def port1(self): return self.spinBox_port1.value() @port1.setter def port1(self, val): self.spinBox_port1.setValue(val) @property def port2(self): return self.spinBox_port2.value() @port2.setter def port2(self, val): self.spinBox_port2.setValue(val) @property def sweep_new(self): return self.checkBox_SweepNew.isChecked() @sweep_new.setter def sweep_new(self, val): self.checkBox_SweepNew.setChecked(val) @property def raw_data(self): return self.checkBox_RawData.isChecked() @raw_data.setter def raw_data(self, val): self.checkBox_RawData.setChecked(val) @property def channel(self): return self.spinBox_channel.value() @channel.setter def channel(self, val): self.spinBox_channel.setValue(val) def control_vna(self): qt.warnMissingFeature()
class PrePostTask(QtCore.QObject, Fysom, metaclass=TaskMetaclass): """ Represents a task that creates the necessary conditions for a different task and reverses its own actions afterwards. """ sigPreExecStart = QtCore.Signal() sigPreExecFinish = QtCore.Signal() sigPostExecStart = QtCore.Signal() sigPostExecFinish = QtCore.Signal() sigStateChanged = QtCore.Signal(object) requiredModules = [] def __init__(self, name, runner, references, config, **kwargs): """ Create a PrePostTask. @param str name: unique name of the task @param object runner: TaskRunner that manages this task @param dict references: contains references to all required modules @param dict config: configuration parameter dictionary """ _default_callbacks = {'onprerun': self._pre, 'onpostrun': self._post} _stateList = { 'initial': 'stopped', 'events': [{ 'name': 'prerun', 'src': 'stopped', 'dst': 'paused' }, { 'name': 'postrun', 'src': 'paused', 'dst': 'stopped' }], 'callbacks': _default_callbacks } if 'PyQt5' in sys.modules: super().__init__(cfg=_stateList, **kwargs) else: QtCore.QObject.__init__(self) Fysom.__init__(self, _stateList) self.lock = Mutex() self.name = name self.runner = runner self.ref = references self.config = config @property def log(self): """ Returns a logger object """ return logging.getLogger("{0}.{1}".format(self.__module__, self.__class__.__name__)) def onchangestate(self, e): """ Fysom callback for all state transitions. @param object e: Fysom state transition description This just emits a signal so external components can react. """ self.sigStateChanged.emit(e) @abc.abstractmethod def preExecute(self): """ This method contains any action that should be done before some task. It needs to be overwritten in every subclass. """ pass @abc.abstractmethod def postExecute(self): """ This method needs to undo any actions in preExecute() after a task has been finished. It needs to be overwritten in every subclass. """ pass def _pre(self, e): """ Actually call preExecute with the appropriate safeguards amd emit singals before and afterwards. @param object e: Fysom state transition description """ self.sigPreExecStart.emit() try: self.preExecute() except Exception as e: self.log.exception('Exception during task {0}. {1}'.format( self.name, e)) self.sigPreExecFinish.emit() def _post(self, e): """ Actually call postExecute with the appropriate safeguards amd emit singals before and afterwards. @param object e: Fysom state transition description """ self.sigPostExecStart.emit() try: self.postExecute() except Exception as e: self.log.exception('Exception during task {0}. {1}'.format( self.name, e)) self.sigPostExecFinish.emit()
class QudiKernelLogic(GenericLogic): """ Logic module providing a Jupyer-compatible kernel connected via ZMQ.""" _modclass = 'QudiKernelLogic' _modtype = 'logic' _out = {'kernel': 'QudiKernelLogic'} sigStartKernel = QtCore.Signal(str) sigStopKernel = QtCore.Signal(int) def __init__(self, **kwargs): """ Create logic object @param dict kwargs: additional parameters as a dict """ super().__init__(**kwargs) self.kernellist = dict() self.modules = set() def on_activate(self, e): """ Prepare logic module for work. @param object e: Fysom state change notification """ logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG) self.kernellist = dict() self.modules = set() self._manager.sigModulesChanged.connect(self.updateModuleList) self.sigStartKernel.connect(self.updateModuleList, QtCore.Qt.QueuedConnection) def on_deactivate(self, e): """ Deactivate module. @param object e: Fysom state change notification """ while len(self.kernellist) > 0: self.stopKernel(tuple(self.kernellist.keys())[0]) QtCore.QCoreApplication.processEvents() time.sleep(0.05) def startKernel(self, config, external=None): """Start a qudi inprocess jupyter kernel. @param dict config: connection information for kernel @param callable external: function to call on exit of kernel @return str: uuid of the started kernel """ realconfig = netobtain(config) self.log.info('Start {0}'.format(realconfig)) mythread = self.getModuleThread() kernel = QZMQKernel(realconfig) kernel.moveToThread(mythread) kernel.user_global_ns.update({ 'pg': pg, 'np': np, 'config': self._manager.tree['defined'], 'manager': self._manager }) kernel.sigShutdownFinished.connect(self.cleanupKernel) self.log.info('Kernel is {0}'.format(kernel.engine_id)) QtCore.QMetaObject.invokeMethod(kernel, 'connect_kernel') self.kernellist[kernel.engine_id] = kernel self.log.info('Finished starting Kernel {0}'.format(kernel.engine_id)) self.sigStartKernel.emit(kernel.engine_id) return kernel.engine_id def stopKernel(self, kernelid): """Tell kernel to close all sockets and stop hearteat thread. @param str kernelid: uuid of kernel to be stopped """ realkernelid = netobtain(kernelid) self.log.info('Stopping {0}'.format(realkernelid)) kernel = self.kernellist[realkernelid] QtCore.QMetaObject.invokeMethod(kernel, 'shutdown') def cleanupKernel(self, kernelid, external=None): """Remove kernel reference and tell rpyc client for that kernel to exit. @param str kernelid: uuid of kernel reference to remove @param callable external: reference to rpyc client exit function """ self.log.info('Cleanup kernel {0}'.format(kernelid)) del self.kernellist[kernelid] if external is not None: try: external.exit() except: self.log.warning('External qudikernel starter did not exit') def updateModuleList(self): """Remove non-existing modules from namespace, add new modules to namespace, update reloaded modules """ currentModules = set() newNamespace = dict() for base in ['hardware', 'logic', 'gui']: for module in self._manager.tree['loaded'][base]: currentModules.add(module) newNamespace[module] = self._manager.tree['loaded'][base][ module] discard = self.modules - currentModules for kernel in self.kernellist: self.kernellist[kernel].user_global_ns.update(newNamespace) for module in discard: for kernel in self.kernellist: self.kernellist[kernel].user_global_ns.pop(module, None) self.modules = currentModules
class FFTView(QtWidgets.QWidget): """ creates the layout for the FFT GUI """ # signals buttonSignal = QtCore.Signal() tableClickSignal = QtCore.Signal(object, object) phaseCheckSignal = QtCore.Signal() def __init__(self, parent=None): super(FFTView, self).__init__(parent) self.grid = QtWidgets.QGridLayout(self) # add splitter for resizing splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) # make table self.FFTTable = QtWidgets.QTableWidget(self) self.FFTTable.resize(800, 800) self.FFTTable.setRowCount(9) self.FFTTable.setColumnCount(2) self.FFTTable.setColumnWidth(0, 300) self.FFTTable.setColumnWidth(1, 300) self.FFTTable.verticalHeader().setVisible(False) self.FFTTable.horizontalHeader().setStretchLastSection(True) self.FFTTable.setHorizontalHeaderLabels( ("FFT Property;Value").split(";")) # populate table options = ['test'] table_utils.setRowName(self.FFTTable, 0, "Workspace") self.ws = table_utils.addComboToTable(self.FFTTable, 0, options) self.Im_box_row = 1 table_utils.setRowName(self.FFTTable, self.Im_box_row, "Imaginary Data") self.Im_box = table_utils.addCheckBoxToTable(self.FFTTable, True, self.Im_box_row) table_utils.setRowName(self.FFTTable, 2, "Imaginary Workspace") self.Im_ws = table_utils.addComboToTable(self.FFTTable, 2, options) self.shift_box_row = 3 table_utils.setRowName(self.FFTTable, self.shift_box_row, "Auto shift") self.shift_box = table_utils.addCheckBoxToTable( self.FFTTable, True, self.shift_box_row) table_utils.setRowName(self.FFTTable, 4, "Shift") self.shift = table_utils.addDoubleToTable(self.FFTTable, 0.0, 4) self.FFTTable.hideRow(4) table_utils.setRowName(self.FFTTable, 5, "Use Raw data") self.Raw_box = table_utils.addCheckBoxToTable(self.FFTTable, True, 5) table_utils.setRowName(self.FFTTable, 6, "First Good Data") self.x0 = table_utils.addDoubleToTable(self.FFTTable, 0.1, 6) self.FFTTable.hideRow(6) table_utils.setRowName(self.FFTTable, 7, "Last Good Data") self.xN = table_utils.addDoubleToTable(self.FFTTable, 15.0, 7) self.FFTTable.hideRow(7) table_utils.setRowName(self.FFTTable, 8, "Construct Phase Table") self.phaseTable_box = table_utils.addCheckBoxToTable( self.FFTTable, True, 8) self.FFTTable.hideRow(8) self.FFTTable.resizeRowsToContents() # make advanced table options self.advancedLabel = QtWidgets.QLabel("\n Advanced Options") self.FFTTableA = QtWidgets.QTableWidget(self) self.FFTTableA.resize(800, 800) self.FFTTableA.setRowCount(4) self.FFTTableA.setColumnCount(2) self.FFTTableA.setColumnWidth(0, 300) self.FFTTableA.setColumnWidth(1, 300) self.FFTTableA.verticalHeader().setVisible(False) self.FFTTableA.horizontalHeader().setStretchLastSection(True) self.FFTTableA.setHorizontalHeaderLabels( ("Advanced Property;Value").split(";")) table_utils.setRowName(self.FFTTableA, 0, "Apodization Function") options = ["Lorentz", "Gaussian", "None"] self.apodization = table_utils.addComboToTable(self.FFTTableA, 0, options) table_utils.setRowName(self.FFTTableA, 1, "Decay Constant (micro seconds)") self.decay = table_utils.addDoubleToTable(self.FFTTableA, 4.4, 1) table_utils.setRowName(self.FFTTableA, 2, "Negative Padding") self.negativePadding = table_utils.addCheckBoxToTable( self.FFTTableA, True, 2) table_utils.setRowName(self.FFTTableA, 3, "Padding") self.padding = table_utils.addSpinBoxToTable(self.FFTTableA, 1, 3) self.FFTTableA.resizeRowsToContents() # make button self.button = QtWidgets.QPushButton('Calculate FFT', self) self.button.setStyleSheet("background-color:lightgrey") # connects self.FFTTable.cellClicked.connect(self.tableClick) self.button.clicked.connect(self.buttonClick) self.ws.currentIndexChanged.connect(self.phaseCheck) # add to layout self.FFTTable.setMinimumSize(40, 158) self.FFTTableA.setMinimumSize(40, 127) table_utils.setTableHeaders(self.FFTTable) table_utils.setTableHeaders(self.FFTTableA) # add to layout splitter.addWidget(self.FFTTable) splitter.addWidget(self.advancedLabel) splitter.addWidget(self.FFTTableA) self.grid.addWidget(splitter) self.grid.addWidget(self.button) def getLayout(self): return self.grid # add data to view def addItems(self, options): self.ws.clear() self.ws.addItems(options) self.ws.addItem("PhaseQuad") self.Im_ws.clear() self.Im_ws.addItems(options) self.phaseQuadChanged() def removeIm(self, pattern): index = self.Im_ws.findText(pattern) self.Im_ws.removeItem(index) def removeRe(self, pattern): index = self.ws.findText(pattern) self.ws.removeItem(index) def setReTo(self, name): index = self.ws.findText(name) if index == -1: return self.ws.setCurrentIndex(index) def setImTo(self, name): index = self.Im_ws.findText(name) if index == -1: return self.Im_ws.setCurrentIndex(index) # connect signals def phaseCheck(self): self.phaseCheckSignal.emit() def tableClick(self, row, col): self.tableClickSignal.emit(row, col) def buttonClick(self): self.buttonSignal.emit() def getInputWS(self): return self.ws.currentText() def getInputImWS(self): return self.Im_ws.currentText() # responses to commands def activateButton(self): self.button.setEnabled(True) def deactivateButton(self): self.button.setEnabled(False) def setPhaseBox(self): self.FFTTable.setRowHidden(8, "PhaseQuad" not in self.getWS()) def changed(self, box, row): self.FFTTable.setRowHidden(row, box.checkState() == QtCore.Qt.Checked) def changedHideUnTick(self, box, row): self.FFTTable.setRowHidden(row, box.checkState() != QtCore.Qt.Checked) def phaseQuadChanged(self): # show axis self.FFTTable.setRowHidden(6, "PhaseQuad" not in self.getWS()) self.FFTTable.setRowHidden(7, "PhaseQuad" not in self.getWS()) # hide complex ws self.FFTTable.setRowHidden(2, "PhaseQuad" in self.getWS()) # these are for getting inputs def getRunName(self): if mantid.AnalysisDataService.doesExist("MuonAnalysis_1"): tmpWS = mantid.AnalysisDataService.retrieve("MuonAnalysis_1") else: tmpWS = mantid.AnalysisDataService.retrieve("MuonAnalysis") return tmpWS.getInstrument().getName() + str( tmpWS.getRunNumber()).zfill(8) def initFFTInput(self, run=None): inputs = {} inputs['InputWorkspace'] = "__ReTmp__" # inputs['Real'] = 0 # always zero out = str(self.ws.currentText()).replace(";", "; ") if run is None: run = self.getRunName() inputs['OutputWorkspace'] = run + ";" + out + ";FFT" inputs["AcceptXRoundingErrors"] = True return inputs def addFFTComplex(self, inputs): inputs["InputImagWorkspace"] = "__ImTmp__" inputs["Imaginary"] = 0 # always zero def addFFTShift(self, inputs): inputs['AutoShift'] = False inputs['Shift'] = float(self.shift.text()) def addRaw(self, inputs, key): inputs[key] += "_Raw" def getFFTRePhase(self, inputs): inputs['InputWorkspace'] = "__ReTmp__" inputs['Real'] = 0 # always zero def getFFTImPhase(self, inputs): inputs['InputImagWorkspace'] = "__ReTmp__" inputs['Imaginary'] = 1 def initAdvanced(self): inputs = {} inputs["ApodizationFunction"] = str(self.apodization.currentText()) inputs["DecayConstant"] = float(self.decay.text()) inputs["NegativePadding"] = self.negativePadding.checkState() inputs["Padding"] = int(self.padding.text()) return inputs def ReAdvanced(self, inputs): inputs['InputWorkspace'] = str(self.ws.currentText()).replace( ";", "; ") inputs['OutputWorkspace'] = "__ReTmp__" def ImAdvanced(self, inputs): inputs['InputWorkspace'] = str(self.Im_ws.currentText()).replace( ";", "; ") inputs['OutputWorkspace'] = "__ImTmp__" def RePhaseAdvanced(self, inputs): inputs['InputWorkspace'] = "__phaseQuad__" inputs['OutputWorkspace'] = "__ReTmp__" # get methods (from the GUI) def getWS(self): return str(self.ws.currentText()).replace(";", "; ") def isAutoShift(self): return self.shift_box.checkState() == QtCore.Qt.Checked def isComplex(self): return self.Im_box.checkState() == QtCore.Qt.Checked def isRaw(self): return self.Raw_box.checkState() == QtCore.Qt.Checked def set_raw_checkbox_state(self, state): if state: self.Raw_box.setCheckState(QtCore.Qt.Checked) else: self.Raw_box.setCheckState(QtCore.Qt.Unchecked) def setup_raw_checkbox_changed(self, slot): self.FFTTable.itemChanged.connect(self.raw_checkbox_changed) self.signal_raw_option_changed = slot def raw_checkbox_changed(self, table_item): if table_item == self.Raw_box: self.signal_raw_option_changed() def getImBoxRow(self): return self.Im_box_row def getShiftBoxRow(self): return self.shift_box_row def getImBox(self): return self.Im_box def getShiftBox(self): return self.shift_box def getFirstGoodData(self): return float(self.x0.text()) def getLastGoodData(self): return (self.xN.text()) def isNewPhaseTable(self): return self.phaseTable_box.checkState() == QtCore.Qt.Checked def isPhaseBoxShown(self): return self.FFTTable.isRowHidden(8) def warning_popup(self, message): warning(message, parent=self)
def __init__(self, config=None, filename=None, output=None): # see labelme/config/default_config.yaml for valid configuration if config is None: config = get_config() self._config = config super(MainWindow, self).__init__() self.setWindowTitle(__appname__) # Whether we need to save or not. self.dirty = False self._noSelectionSlot = False # Main widgets and related state. self.labelDialog = LabelDialog( parent=self, labels=self._config['labels'], sort_labels=self._config['sort_labels'], show_text_field=self._config['show_label_text_field'], ) self.labelList = LabelQListWidget() self.lastOpenDir = None self.labelList.itemActivated.connect(self.labelSelectionChanged) self.labelList.itemSelectionChanged.connect(self.labelSelectionChanged) self.labelList.itemDoubleClicked.connect(self.editLabel) # Connect to itemChanged to detect checkbox changes. self.labelList.itemChanged.connect(self.labelItemChanged) self.labelList.setDragDropMode( QtWidgets.QAbstractItemView.InternalMove) self.labelList.setParent(self) listLayout = QtWidgets.QVBoxLayout() listLayout.setContentsMargins(0, 0, 0, 0) self.editButton = QtWidgets.QToolButton() self.editButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) listLayout.addWidget(self.editButton) # 0, Qt.AlignCenter) listLayout.addWidget(self.labelList) self.labelListContainer = QtWidgets.QWidget() self.labelListContainer.setLayout(listLayout) self.flag_dock = self.flag_widget = None self.flag_dock = QtWidgets.QDockWidget('Flags', self) self.flag_dock.setObjectName('Flags') self.flag_widget = QtWidgets.QListWidget() if config['flags']: self.loadFlags({k: False for k in config['flags']}) self.flag_dock.setWidget(self.flag_widget) self.flag_widget.itemChanged.connect(self.setDirty) self.uniqLabelList = EscapableQListWidget() self.uniqLabelList.setToolTip( "Select label to start annotating for it. " "Press 'Esc' to deselect.") if self._config['labels']: self.uniqLabelList.addItems(self._config['labels']) self.uniqLabelList.sortItems() self.labelsdock = QtWidgets.QDockWidget(u'Label List', self) self.labelsdock.setObjectName(u'Label List') self.labelsdock.setWidget(self.uniqLabelList) self.dock = QtWidgets.QDockWidget('Polygon Labels', self) self.dock.setObjectName('Labels') self.dock.setWidget(self.labelListContainer) self.fileListWidget = QtWidgets.QListWidget() self.fileListWidget.itemSelectionChanged.connect( self.fileSelectionChanged) filelistLayout = QtWidgets.QVBoxLayout() filelistLayout.setContentsMargins(0, 0, 0, 0) filelistLayout.addWidget(self.fileListWidget) fileListContainer = QtWidgets.QWidget() fileListContainer.setLayout(filelistLayout) self.filedock = QtWidgets.QDockWidget(u'File List', self) self.filedock.setObjectName(u'Files') self.filedock.setWidget(fileListContainer) self.zoomWidget = ZoomWidget() self.colorDialog = ColorDialog(parent=self) self.canvas = self.labelList.canvas = Canvas( epsilon=self._config['epsilon'], ) self.canvas.zoomRequest.connect(self.zoomRequest) scrollArea = QtWidgets.QScrollArea() scrollArea.setWidget(self.canvas) scrollArea.setWidgetResizable(True) self.scrollBars = { Qt.Vertical: scrollArea.verticalScrollBar(), Qt.Horizontal: scrollArea.horizontalScrollBar(), } self.canvas.scrollRequest.connect(self.scrollRequest) self.canvas.newShape.connect(self.newShape) self.canvas.shapeMoved.connect(self.setDirty) self.canvas.selectionChanged.connect(self.shapeSelectionChanged) self.canvas.drawingPolygon.connect(self.toggleDrawingSensitive) self.setCentralWidget(scrollArea) self.addDockWidget(Qt.RightDockWidgetArea, self.flag_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.labelsdock) self.addDockWidget(Qt.RightDockWidgetArea, self.dock) self.addDockWidget(Qt.RightDockWidgetArea, self.filedock) # Actions action = functools.partial(newAction, self) shortcuts = self._config['shortcuts'] quit = action('&Quit', self.close, shortcuts['quit'], 'quit', 'Quit application') open_ = action('&Open', self.openFile, shortcuts['open'], 'open', 'Open image or label file') opendir = action('&Open Dir', self.openDirDialog, shortcuts['open_dir'], 'open', u'Open Dir') openNextImg = action('&Next Image', self.openNextImg, shortcuts['open_next'], 'next', u'Open Next') openPrevImg = action('&Prev Image', self.openPrevImg, shortcuts['open_prev'], 'prev', u'Open Prev') save = action('&Save', self.saveFile, shortcuts['save'], 'save', 'Save labels to file', enabled=False) saveAs = action('&Save As', self.saveFileAs, shortcuts['save_as'], 'save-as', 'Save labels to a different file', enabled=False) close = action('&Close', self.closeFile, shortcuts['close'], 'close', 'Close current file') color1 = action('Polygon &Line Color', self.chooseColor1, shortcuts['edit_line_color'], 'color_line', 'Choose polygon line color') color2 = action('Polygon &Fill Color', self.chooseColor2, shortcuts['edit_fill_color'], 'color', 'Choose polygon fill color') createMode = action( 'Create Polygons', lambda: self.toggleDrawMode(False, createMode='polygon'), shortcuts['create_polygon'], 'objects', 'Start drawing polygons', enabled=True, ) createRectangleMode = action( 'Create Rectangle', lambda: self.toggleDrawMode(False, createMode='rectangle'), shortcuts['create_rectangle'], 'objects', 'Start drawing rectangles', enabled=True, ) createLineMode = action( 'Create Line', lambda: self.toggleDrawMode(False, createMode='line'), shortcuts['create_line'], 'objects', 'Start drawing lines', enabled=True, ) createPointMode = action( 'Create Point', lambda: self.toggleDrawMode(False, createMode='point'), shortcuts['create_point'], 'objects', 'Start drawing points', enabled=True, ) editMode = action('Edit Polygons', self.setEditMode, shortcuts['edit_polygon'], 'edit', 'Move and edit polygons', enabled=True) delete = action('Delete Polygon', self.deleteSelectedShape, shortcuts['delete_polygon'], 'cancel', 'Delete', enabled=False) copy = action('Duplicate Polygon', self.copySelectedShape, shortcuts['duplicate_polygon'], 'copy', 'Create a duplicate of the selected polygon', enabled=False) undoLastPoint = action('Undo last point', self.canvas.undoLastPoint, shortcuts['undo_last_point'], 'undo', 'Undo last drawn point', enabled=False) addPoint = action('Add Point to Edge', self.canvas.addPointToEdge, None, 'edit', 'Add point to the nearest edge', enabled=False) undo = action('Undo', self.undoShapeEdit, shortcuts['undo'], 'undo', 'Undo last add and edit of shape', enabled=False) hideAll = action('&Hide\nPolygons', functools.partial(self.togglePolygons, False), icon='eye', tip='Hide all polygons', enabled=False) showAll = action('&Show\nPolygons', functools.partial(self.togglePolygons, True), icon='eye', tip='Show all polygons', enabled=False) help = action('&Tutorial', self.tutorial, icon='help', tip='Show tutorial page') zoom = QtWidgets.QWidgetAction(self) zoom.setDefaultWidget(self.zoomWidget) self.zoomWidget.setWhatsThis( "Zoom in or out of the image. Also accessible with" " %s and %s from the canvas." % (fmtShortcut('%s,%s' % (shortcuts['zoom_in'], shortcuts['zoom_out'])), fmtShortcut("Ctrl+Wheel"))) self.zoomWidget.setEnabled(False) zoomIn = action('Zoom &In', functools.partial(self.addZoom, 10), shortcuts['zoom_in'], 'zoom-in', 'Increase zoom level', enabled=False) zoomOut = action('&Zoom Out', functools.partial(self.addZoom, -10), shortcuts['zoom_out'], 'zoom-out', 'Decrease zoom level', enabled=False) zoomOrg = action('&Original size', functools.partial(self.setZoom, 100), shortcuts['zoom_to_original'], 'zoom', 'Zoom to original size', enabled=False) fitWindow = action('&Fit Window', self.setFitWindow, shortcuts['fit_window'], 'fit-window', 'Zoom follows window size', checkable=True, enabled=False) fitWidth = action('Fit &Width', self.setFitWidth, shortcuts['fit_width'], 'fit-width', 'Zoom follows window width', checkable=True, enabled=False) # Group zoom controls into a list for easier toggling. zoomActions = (self.zoomWidget, zoomIn, zoomOut, zoomOrg, fitWindow, fitWidth) self.zoomMode = self.MANUAL_ZOOM self.scalers = { self.FIT_WINDOW: self.scaleFitWindow, self.FIT_WIDTH: self.scaleFitWidth, # Set to one to scale to 100% when loading files. self.MANUAL_ZOOM: lambda: 1, } edit = action('&Edit Label', self.editLabel, shortcuts['edit_label'], 'edit', 'Modify the label of the selected polygon', enabled=False) self.editButton.setDefaultAction(edit) shapeLineColor = action( 'Shape &Line Color', self.chshapeLineColor, icon='color-line', tip='Change the line color for this specific shape', enabled=False) shapeFillColor = action( 'Shape &Fill Color', self.chshapeFillColor, icon='color', tip='Change the fill color for this specific shape', enabled=False) labels = self.dock.toggleViewAction() labels.setText('Show/Hide Label Panel') # Lavel list context menu. labelMenu = QtWidgets.QMenu() addActions(labelMenu, (edit, delete)) self.labelList.setContextMenuPolicy(Qt.CustomContextMenu) self.labelList.customContextMenuRequested.connect( self.popLabelListMenu) # Store actions for further handling. self.actions = struct( save=save, saveAs=saveAs, open=open_, close=close, lineColor=color1, fillColor=color2, delete=delete, edit=edit, copy=copy, undoLastPoint=undoLastPoint, undo=undo, addPoint=addPoint, createMode=createMode, editMode=editMode, createRectangleMode=createRectangleMode, createLineMode=createLineMode, createPointMode=createPointMode, shapeLineColor=shapeLineColor, shapeFillColor=shapeFillColor, zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg, fitWindow=fitWindow, fitWidth=fitWidth, zoomActions=zoomActions, fileMenuActions=(open_, opendir, save, saveAs, close, quit), tool=(), editMenu=(edit, copy, delete, None, undo, undoLastPoint, None, color1, color2), # menu shown at right click menu=( createMode, createRectangleMode, createLineMode, createPointMode, editMode, edit, copy, delete, shapeLineColor, shapeFillColor, undo, undoLastPoint, addPoint, ), onLoadActive=( close, createMode, createRectangleMode, createLineMode, createPointMode, editMode, ), onShapesPresent=(saveAs, hideAll, showAll), ) self.canvas.edgeSelected.connect(self.actions.addPoint.setEnabled) self.menus = struct( file=self.menu('&File'), edit=self.menu('&Edit'), view=self.menu('&View'), help=self.menu('&Help'), recentFiles=QtWidgets.QMenu('Open &Recent'), labelList=labelMenu, ) addActions(self.menus.file, (open_, openNextImg, openPrevImg, opendir, self.menus.recentFiles, save, saveAs, close, None, quit)) addActions(self.menus.help, (help,)) addActions(self.menus.view, ( labels, None, hideAll, showAll, None, zoomIn, zoomOut, zoomOrg, None, fitWindow, fitWidth)) self.menus.file.aboutToShow.connect(self.updateFileMenu) # Custom context menu for the canvas widget: addActions(self.canvas.menus[0], self.actions.menu) addActions(self.canvas.menus[1], ( action('&Copy here', self.copyShape), action('&Move here', self.moveShape))) self.tools = self.toolbar('Tools') # Menu buttons on Left self.actions.tool = ( open_, opendir, openNextImg, openPrevImg, save, None, createMode, editMode, copy, delete, undo, None, zoomIn, zoom, zoomOut, fitWindow, fitWidth, ) self.statusBar().showMessage('%s started.' % __appname__) self.statusBar().show() # Application state. self.image = QtGui.QImage() self.imagePath = None if self._config['auto_save'] and output is not None: warnings.warn('If `auto_save` argument is True, `output` argument ' 'is ignored and output filename is automatically ' 'set as IMAGE_BASENAME.json.') self.labeling_once = output is not None self.output = output self.recentFiles = [] self.maxRecent = 7 self.lineColor = None self.fillColor = None self.otherData = None self.zoom_level = 100 self.fit_window = False if filename is not None and os.path.isdir(filename): self.importDirImages(filename, load=False) else: self.filename = filename # XXX: Could be completely declarative. # Restore application settings. self.settings = QtCore.QSettings('labelme', 'labelme') # FIXME: QSettings.value can return None on PyQt4 self.recentFiles = self.settings.value('recentFiles', []) or [] size = self.settings.value('window/size', QtCore.QSize(600, 500)) position = self.settings.value('window/position', QtCore.QPoint(0, 0)) self.resize(size) self.move(position) # or simply: # self.restoreGeometry(settings['window/geometry'] self.restoreState( self.settings.value('window/state', QtCore.QByteArray())) self.lineColor = QtGui.QColor( self.settings.value('line/color', Shape.line_color)) self.fillColor = QtGui.QColor( self.settings.value('fill/color', Shape.fill_color)) Shape.line_color = self.lineColor Shape.fill_color = self.fillColor # Populate the File menu dynamically. self.updateFileMenu() # Since loading the file may take some time, # make sure it runs in the background. if self.filename is not None: self.queueEvent(functools.partial(self.loadFile, self.filename)) # Callbacks: self.zoomWidget.valueChanged.connect(self.paintCanvas) self.populateModeActions()
def __init__(self, parent, shortcuts: ClientGUIShortcuts.ShortcutSet): ClientGUIScrolledPanels.EditPanel.__init__(self, parent) self._name = QW.QLineEdit(self) self._shortcuts_panel = ClientGUIListCtrl.BetterListCtrlPanel(self) self._shortcuts = ClientGUIListCtrl.BetterListCtrl( self._shortcuts_panel, CGLC.COLUMN_LIST_SHORTCUTS.ID, 20, data_to_tuples_func=self._ConvertSortTupleToPrettyTuple, delete_key_callback=self.RemoveShortcuts, activation_callback=self.EditShortcuts) self._shortcuts_panel.SetListCtrl(self._shortcuts) self._shortcuts_panel.AddImportExportButtons( (ClientGUIShortcuts.ShortcutSet, ), self._AddShortcutSet, custom_get_callable=self._GetSelectedShortcutSet) self._shortcuts.setMinimumSize(QC.QSize(360, 480)) self._add = QW.QPushButton('add', self) self._add.clicked.connect(self.AddShortcut) self._edit = QW.QPushButton('edit', self) self._edit.clicked.connect(self.EditShortcuts) self._remove = QW.QPushButton('remove', self) self._remove.clicked.connect(self.RemoveShortcuts) # name = shortcuts.GetName() self._name.setText(name) self._this_is_custom = True if name in ClientGUIShortcuts.SHORTCUTS_RESERVED_NAMES: self._this_is_custom = False self._name.setEnabled(False) self._shortcuts.AddDatas(shortcuts) self._shortcuts.Sort() # action_buttons = QP.HBoxLayout() QP.AddToLayout(action_buttons, self._add, CC.FLAGS_CENTER_PERPENDICULAR) QP.AddToLayout(action_buttons, self._edit, CC.FLAGS_CENTER_PERPENDICULAR) QP.AddToLayout(action_buttons, self._remove, CC.FLAGS_CENTER_PERPENDICULAR) vbox = QP.VBoxLayout() QP.AddToLayout(vbox, ClientGUICommon.WrapInText(self._name, self, 'name: '), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR) if name in ClientGUIShortcuts.shortcut_names_to_descriptions: description_text = ClientGUIShortcuts.shortcut_names_to_descriptions[ name] description = ClientGUICommon.BetterStaticText( self, description_text, description_text) description.setWordWrap(True) QP.AddToLayout(vbox, description, CC.FLAGS_EXPAND_PERPENDICULAR) QP.AddToLayout(vbox, self._shortcuts_panel, CC.FLAGS_EXPAND_BOTH_WAYS) QP.AddToLayout(vbox, action_buttons, CC.FLAGS_ON_RIGHT) self.widget().setLayout(vbox)
class MouseShortcutButton(QW.QPushButton): valueChanged = QC.Signal() def __init__(self, parent): self._shortcut = ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, []) self._press_instead_of_release = True QW.QPushButton.__init__(self, parent) self._SetShortcutString() def _ProcessMouseEvent(self, event): self.setFocus(QC.Qt.OtherFocusReason) shortcut = ClientGUIShortcuts.ConvertMouseEventToShortcut(event) if shortcut is not None: self._shortcut = shortcut self._SetShortcutString() self.valueChanged.emit() def _SetShortcutString(self): display_string = self._shortcut.ToString() self.setText(display_string) def mousePressEvent(self, event): if self._press_instead_of_release: self._ProcessMouseEvent(event) def mouseReleaseEvent(self, event): if not self._press_instead_of_release: self._ProcessMouseEvent(event) def mouseDoubleClickEvent(self, event): self._ProcessMouseEvent(event) def wheelEvent(self, event): self._ProcessMouseEvent(event) def GetValue(self) -> ClientGUIShortcuts.Shortcut: return self._shortcut def SetPressInsteadOfRelease(self, press_instead_of_release): self._press_instead_of_release = press_instead_of_release if self._shortcut.IsAppropriateForPressRelease(): self._shortcut = self._shortcut.Duplicate() if self._press_instead_of_release: self._shortcut.shortcut_press_type = ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS else: self._shortcut.shortcut_press_type = ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_RELEASE self._SetShortcutString() self.valueChanged.emit() def SetValue(self, shortcut: ClientGUIShortcuts.Shortcut): self._shortcut = shortcut.Duplicate() self._SetShortcutString() self.valueChanged.emit()
def __init__(self, parent, all_shortcuts): ClientGUIScrolledPanels.EditPanel.__init__(self, parent) help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp) help_button.setToolTip('Show help regarding editing shortcuts.') reserved_panel = ClientGUICommon.StaticBox( self, 'built-in hydrus shortcut sets') self._reserved_shortcuts = ClientGUIListCtrl.BetterListCtrl( reserved_panel, CGLC.COLUMN_LIST_SHORTCUT_SETS.ID, 6, data_to_tuples_func=self._GetTuples, activation_callback=self._EditReserved) self._reserved_shortcuts.setMinimumSize(QC.QSize(320, 200)) self._edit_reserved_button = ClientGUICommon.BetterButton( reserved_panel, 'edit', self._EditReserved) self._restore_defaults_button = ClientGUICommon.BetterButton( reserved_panel, 'restore defaults', self._RestoreDefaults) # custom_panel = ClientGUICommon.StaticBox(self, 'custom user sets') self._custom_shortcuts = ClientGUIListCtrl.BetterListCtrl( custom_panel, CGLC.COLUMN_LIST_SHORTCUT_SETS.ID, 6, data_to_tuples_func=self._GetTuples, delete_key_callback=self._Delete, activation_callback=self._EditCustom) self._add_button = ClientGUICommon.BetterButton( custom_panel, 'add', self._Add) self._edit_custom_button = ClientGUICommon.BetterButton( custom_panel, 'edit', self._EditCustom) self._delete_button = ClientGUICommon.BetterButton( custom_panel, 'delete', self._Delete) if not HG.client_controller.new_options.GetBoolean('advanced_mode'): custom_panel.hide() # reserved_shortcuts = [ shortcuts for shortcuts in all_shortcuts if shortcuts.GetName() in ClientGUIShortcuts.SHORTCUTS_RESERVED_NAMES ] custom_shortcuts = [ shortcuts for shortcuts in all_shortcuts if shortcuts.GetName() not in ClientGUIShortcuts.SHORTCUTS_RESERVED_NAMES ] self._reserved_shortcuts.AddDatas(reserved_shortcuts) self._reserved_shortcuts.Sort() self._original_custom_names = set() for shortcuts in custom_shortcuts: self._custom_shortcuts.AddDatas((shortcuts, )) self._original_custom_names.add(shortcuts.GetName()) self._custom_shortcuts.Sort() # button_hbox = QP.HBoxLayout() QP.AddToLayout(button_hbox, self._edit_reserved_button, CC.FLAGS_CENTER_PERPENDICULAR) QP.AddToLayout(button_hbox, self._restore_defaults_button, CC.FLAGS_CENTER_PERPENDICULAR) reserved_panel.Add(self._reserved_shortcuts, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS) reserved_panel.Add(button_hbox, CC.FLAGS_ON_RIGHT) # button_hbox = QP.HBoxLayout() QP.AddToLayout(button_hbox, self._add_button, CC.FLAGS_CENTER_PERPENDICULAR) QP.AddToLayout(button_hbox, self._edit_custom_button, CC.FLAGS_CENTER_PERPENDICULAR) QP.AddToLayout(button_hbox, self._delete_button, CC.FLAGS_CENTER_PERPENDICULAR) custom_panel_message = 'Custom shortcuts are advanced. They apply to the media viewer and must be turned on to take effect.' st = ClientGUICommon.BetterStaticText(custom_panel, custom_panel_message) st.setWordWrap(True) custom_panel.Add(st, CC.FLAGS_EXPAND_PERPENDICULAR) custom_panel.Add(self._custom_shortcuts, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS) custom_panel.Add(button_hbox, CC.FLAGS_ON_RIGHT) # vbox = QP.VBoxLayout() QP.AddToLayout(vbox, help_button, CC.FLAGS_ON_RIGHT) QP.AddToLayout(vbox, reserved_panel, CC.FLAGS_EXPAND_BOTH_WAYS) QP.AddToLayout(vbox, custom_panel, CC.FLAGS_EXPAND_BOTH_WAYS) self.widget().setLayout(vbox)
class BasicMonitor(QTableWidget): """ 基础监控 headerDict中的值对应的字典格式如下 {'chinese': u'中文名', 'cellType': BasicCell} """ signal = QtCore.Signal(type(Event())) # ---------------------------------------------------------------------- def __init__(self, mainEngine=None, eventEngine=None, parent=None): """Constructor""" super(BasicMonitor, self).__init__(parent) self.mainEngine = mainEngine self.eventEngine = eventEngine # 保存表头标签用 self.headerDict = OrderedDict() # 有序字典,key是英文名,value是对应的配置字典 self.headerList = [] # 对应self.headerDict.keys() # 保存相关数据用 self.dataDict = {} # 字典,key是字段对应的数据,value是保存相关单元格的字典 self.dataKey = '' # 字典键对应的数据字段 # 监控的事件类型 self.eventType = '' # 列宽调整状态(只在第一次更新数据时调整一次列宽) self.columnResized = False # 字体 self.font = None self.mouseStatus = False # 保存数据对象到单元格 self.saveData = False # 默认不允许根据表头进行排序,需要的组件可以开启 self.sorting = False self.daylinewidget = None self.search = searchcode() self.stockItemText = None # 设置悬停显示 self.setMouseTracking(True) self.itemEntered.connect(self.handleItemClicked) def mousePressEvent(self, e: QtGui.QMouseEvent): print("press") code = self.search.findbychinese(self.stockItemText) if (len(code) == 0): return print(code[0]) self.mouseStatus = True if (e.button() == Qt.LeftButton): self.daylineshow(code[0], "left") elif (e.button() == Qt.RightButton): self.daylineshow(code[0], "right") elif (e.button() == Qt.MidButton): self.mouseStatus = False def mouseReleaseEvent(self, e: QtGui.QMouseEvent): print("release") self.daylinehide() self.mouseStatus = False def handleItemClicked(self, item): self.stockItemText = item.text() print(item.text()) # ---------------------------------------------------------------------- def setEventType(self, eventType): """设置监控的事件类型""" self.eventType = eventType def registerEvent(self): """注册GUI更新相关的事件监听""" self.signal.connect(self.updateEvent) self.eventEngine.register(self.eventType, self.signal.emit) # ---------------------------------------------------------------------- def updateEvent(self, event): """收到事件更新""" data = event.dict_['data'] self.updateData(data) def daylineinit(self): url = 'http://image.sinajs.cn/newchart/min/n/sh000001.gif' req = requests.get(url) photo = QPixmap() photo.loadFromData(req.content) label = QLabel() label.setPixmap(photo) widget = QWidget() layout = QVBoxLayout() widget.setLayout(layout) layout.addWidget(label) return widget def daylineshow(self, code, str): if (code[0] == '6'): if (str == "left"): url = 'http://image.sinajs.cn/newchart/min/n/sh' + code + '.gif' elif (str == "right"): url = 'http://image.sinajs.cn/newchart/daily/n/sh' + code + '.gif' else: if (str == "left"): url = 'http://image.sinajs.cn/newchart/min/n/sz' + code + '.gif' elif (str == "right"): url = 'http://image.sinajs.cn/newchart/daily/n/sz' + code + '.gif' # url = url.match("sh000001","300279") print(url) req = requests.get(url) photo = QPixmap() photo.loadFromData(req.content) label = QLabel() label.setPixmap(photo) label.setWindowOpacity(0.2) self.daylinewidget = QWidget() self.daylinewidget.setAutoFillBackground(True) palette = QPalette() palette.setColor(QPalette.Background, QColor(128, 128, 128, 50)) self.daylinewidget.setPalette(palette) self.daylinewidget.resize(50, 50) label.setPalette(palette) layout = QVBoxLayout() self.daylinewidget.setLayout(layout) layout.addWidget(label) self.daylinewidget.setGeometry(500, 300, 50, 50) self.daylinewidget.show() def daylinehide(self): if (self.mouseStatus == True): self.daylinewidget.hide()
def style_widgets(self): icon_size = QtCore.QSize(17, 17) self.clear_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'reset_dark.ico'))) self.clear_btn.setIconSize(icon_size) self.add_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'open.ico'))) self.add_btn.setIconSize(icon_size) self.delete_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'delete.png'))) self.delete_btn.setIconSize(QtCore.QSize(12, 14)) self.move_up_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'arrow_up.ico'))) self.move_up_btn.setIconSize(icon_size) self.move_down_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'arrow_down.ico'))) self.move_down_btn.setIconSize(icon_size) def modify_btn_to_icon_size(btn): button_height = 25 button_width = 25 btn.setMinimumHeight(button_height) btn.setMaximumHeight(button_height) btn.setMinimumWidth(button_width) btn.setMaximumWidth(button_width) modify_btn_to_icon_size(self.add_btn) modify_btn_to_icon_size(self.delete_btn) modify_btn_to_icon_size(self.clear_btn) modify_btn_to_icon_size(self.move_up_btn) modify_btn_to_icon_size(self.move_down_btn) step_txt_width = 70 self.scale_step_msb.setMaximumWidth(step_txt_width) self.scale_step_msb.setMinimumWidth(step_txt_width) self.offset_step_msb.setMaximumWidth(step_txt_width) self.waterfall_separation_msb.setMaximumWidth(step_txt_width) self.scale_step_msb.setMaximum(10.0) self.scale_step_msb.setMinimum(0.01) self.scale_step_msb.setValue(0.01) self.offset_step_msb.setMaximum(100000.0) self.offset_step_msb.setMinimum(0.01) self.offset_step_msb.setValue(100.0) self.waterfall_separation_msb.setMaximum(100000.0) self.waterfall_separation_msb.setMinimum(0.01) self.waterfall_separation_msb.setValue(100.0) self.set_as_bkg_btn.setStyleSheet('font-size: 11px') self.waterfall_btn.setMaximumWidth(step_txt_width) self.waterfall_reset_btn.setMaximumWidth(step_txt_width) self.set_as_bkg_btn.setMinimumHeight(40) self.set_as_bkg_btn.setMaximumHeight(40) self.overlay_header_btn.setStyleSheet("border-radius: 0px")
class OverlayWidget(QtWidgets.QWidget): color_btn_clicked = QtCore.Signal(int, QtWidgets.QWidget) show_cb_state_changed = QtCore.Signal(int, bool) name_changed = QtCore.Signal(int, str) scale_sb_value_changed = QtCore.Signal(int, float) offset_sb_value_changed = QtCore.Signal(int, float) def __init__(self): super(OverlayWidget, self).__init__() self._layout = QtWidgets.QHBoxLayout() self._layout.setContentsMargins(5, 5, 5, 5) self._layout.setSpacing(5) self.button_widget = QtWidgets.QWidget(self) self.button_widget.setObjectName('overlay_control_widget') self._button_layout = QtWidgets.QVBoxLayout(self.button_widget) self._button_layout.setContentsMargins(0, 0, 0, 0) self._button_layout.setSpacing(6) self.add_btn = FlatButton() self.delete_btn = FlatButton() self.clear_btn = FlatButton() self.move_up_btn = FlatButton() self.move_down_btn = FlatButton() self._button_layout.addWidget(self.add_btn) self._button_layout.addWidget(self.delete_btn) self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(HorizontalLine()) self._button_layout.addSpacerItem(VerticalSpacerItem()) self._button_layout.addWidget(self.clear_btn) self._button_layout.addSpacerItem(VerticalSpacerItem()) self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(self.move_up_btn) self._button_layout.addWidget(self.move_down_btn) self._layout.addWidget(self.button_widget) self.parameter_widget = QtWidgets.QWidget(self) self._parameter_layout = QtWidgets.QVBoxLayout() self._parameter_layout.setContentsMargins(0, 0, 0, 0) self._parameter_layout.setSpacing(5) self.scale_step_msb = DoubleMultiplySpinBoxAlignRight() self.offset_step_msb = DoubleMultiplySpinBoxAlignRight() self._step_gb = QtWidgets.QWidget() self._step_layout = QtWidgets.QVBoxLayout() self._step_layout.setContentsMargins(0, 0, 0, 0) self._step_layout.setSpacing(4) self._step_layout.addWidget(QtWidgets.QLabel('Scale Step')) self._step_layout.addWidget(self.scale_step_msb) self._step_layout.addWidget(QtWidgets.QLabel('Offset Step')) self._step_layout.addWidget(self.offset_step_msb) self._step_gb.setLayout(self._step_layout) self._parameter_layout.addWidget(self._step_gb) self._parameter_layout.addWidget(HorizontalLine()) self._waterfall_gb = QtWidgets.QWidget() self.waterfall_separation_msb = DoubleMultiplySpinBoxAlignRight() self.waterfall_btn = FlatButton('Waterfall') self.waterfall_reset_btn = FlatButton('Reset') self._waterfall_layout = QtWidgets.QVBoxLayout() self._waterfall_layout.setContentsMargins(0, 0, 0, 0) self._waterfall_layout.setSpacing(4) self._waterfall_layout.addWidget(self.waterfall_btn) self._waterfall_layout.addWidget(self.waterfall_separation_msb) self._waterfall_layout.addWidget(self.waterfall_reset_btn) self._waterfall_gb.setLayout(self._waterfall_layout) self._parameter_layout.addWidget(self._waterfall_gb) self._parameter_layout.addWidget(HorizontalLine()) self._parameter_layout.addItem(VerticalSpacerItem()) self.set_as_bkg_btn = CheckableFlatButton('Set as\nBackground') self._background_layout = QtWidgets.QHBoxLayout() self._background_layout.setContentsMargins(0, 0, 0, 0) self._background_layout.addSpacerItem(HorizontalSpacerItem()) self._background_layout.addWidget(self.set_as_bkg_btn) self._parameter_layout.addLayout(self._background_layout) self.parameter_widget.setLayout(self._parameter_layout) self.overlay_tw = ListTableWidget(columns=5) self.overlay_tw.setObjectName('overlay_table_widget') self.overlay_tw.setHorizontalHeaderLabels( ['', '', 'Name', 'Scale', 'Offset']) self.overlay_tw.horizontalHeader().setVisible(True) self.overlay_tw.horizontalHeader().setStretchLastSection(False) self.overlay_tw.horizontalHeader().setResizeMode( 2, QtWidgets.QHeaderView.Stretch) self.overlay_tw.horizontalHeader().setResizeMode( 3, QtWidgets.QHeaderView.ResizeToContents) self.overlay_tw.horizontalHeader().setResizeMode( 4, QtWidgets.QHeaderView.ResizeToContents) self.overlay_tw.setColumnWidth(0, 20) self.overlay_tw.setColumnWidth(1, 25) self.overlay_tw.cellChanged.connect(self.label_editingFinished) self.overlay_tw.setItemDelegate(NoRectDelegate()) self._layout.addWidget(self.overlay_tw, 10) self._layout.addWidget(self.parameter_widget, 0) # label for alternative view: self.overlay_header_btn = FlatButton('Overlay') self.overlay_header_btn.setEnabled(False) self.overlay_header_btn.setVisible(False) self._main_layout = QtWidgets.QVBoxLayout() self._main_layout.setContentsMargins(0, 0, 0, 0) self._main_layout.addWidget(self.overlay_header_btn) self._main_layout.addLayout(self._layout) self.setLayout(self._main_layout) self.style_widgets() self.add_tooltips() self.show_cbs = [] self.color_btns = [] self.scale_sbs = [] self.offset_sbs = [] def style_widgets(self): icon_size = QtCore.QSize(17, 17) self.clear_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'reset_dark.ico'))) self.clear_btn.setIconSize(icon_size) self.add_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'open.ico'))) self.add_btn.setIconSize(icon_size) self.delete_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'delete.png'))) self.delete_btn.setIconSize(QtCore.QSize(12, 14)) self.move_up_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'arrow_up.ico'))) self.move_up_btn.setIconSize(icon_size) self.move_down_btn.setIcon( QtGui.QIcon(os.path.join(icons_path, 'arrow_down.ico'))) self.move_down_btn.setIconSize(icon_size) def modify_btn_to_icon_size(btn): button_height = 25 button_width = 25 btn.setMinimumHeight(button_height) btn.setMaximumHeight(button_height) btn.setMinimumWidth(button_width) btn.setMaximumWidth(button_width) modify_btn_to_icon_size(self.add_btn) modify_btn_to_icon_size(self.delete_btn) modify_btn_to_icon_size(self.clear_btn) modify_btn_to_icon_size(self.move_up_btn) modify_btn_to_icon_size(self.move_down_btn) step_txt_width = 70 self.scale_step_msb.setMaximumWidth(step_txt_width) self.scale_step_msb.setMinimumWidth(step_txt_width) self.offset_step_msb.setMaximumWidth(step_txt_width) self.waterfall_separation_msb.setMaximumWidth(step_txt_width) self.scale_step_msb.setMaximum(10.0) self.scale_step_msb.setMinimum(0.01) self.scale_step_msb.setValue(0.01) self.offset_step_msb.setMaximum(100000.0) self.offset_step_msb.setMinimum(0.01) self.offset_step_msb.setValue(100.0) self.waterfall_separation_msb.setMaximum(100000.0) self.waterfall_separation_msb.setMinimum(0.01) self.waterfall_separation_msb.setValue(100.0) self.set_as_bkg_btn.setStyleSheet('font-size: 11px') self.waterfall_btn.setMaximumWidth(step_txt_width) self.waterfall_reset_btn.setMaximumWidth(step_txt_width) self.set_as_bkg_btn.setMinimumHeight(40) self.set_as_bkg_btn.setMaximumHeight(40) self.overlay_header_btn.setStyleSheet("border-radius: 0px") def add_tooltips(self): self.add_btn.setToolTip('Loads Overlay(s) from file(s)') self.delete_btn.setToolTip('Removes currently selected overlay') self.clear_btn.setToolTip('Removes all overlays') def add_overlay(self, name, color): current_rows = self.overlay_tw.rowCount() self.overlay_tw.setRowCount(current_rows + 1) self.overlay_tw.blockSignals(True) show_cb = QtWidgets.QCheckBox() show_cb.setChecked(True) show_cb.stateChanged.connect(partial(self.show_cb_changed, show_cb)) show_cb.setStyleSheet("background-color: transparent") self.overlay_tw.setCellWidget(current_rows, 0, show_cb) self.show_cbs.append(show_cb) color_button = FlatButton() color_button.setStyleSheet("background-color: " + color) color_button.clicked.connect( partial(self.color_btn_click, color_button)) self.overlay_tw.setCellWidget(current_rows, 1, color_button) self.color_btns.append(color_button) name_item = QtWidgets.QTableWidgetItem(name) name_item.setFlags(name_item.flags() & ~QtCore.Qt.ItemIsEditable) self.overlay_tw.setItem(current_rows, 2, QtWidgets.QTableWidgetItem(name)) scale_sb = DoubleSpinBoxAlignRight() scale_sb.setMinimum(-9999999) scale_sb.setMaximum(9999999) scale_sb.setValue(1) scale_sb.setSingleStep(self.scale_step_msb.value()) scale_sb.valueChanged.connect(partial(self.scale_sb_callback, scale_sb)) self.overlay_tw.setCellWidget(current_rows, 3, scale_sb) self.scale_sbs.append(scale_sb) offset_sb = DoubleSpinBoxAlignRight() offset_sb.setMinimum(-9999999) offset_sb.setMaximum(9999999) offset_sb.setValue(0) offset_sb.setSingleStep(self.offset_step_msb.value()) offset_sb.valueChanged.connect( partial(self.offset_sb_callback, offset_sb)) self.overlay_tw.setCellWidget(current_rows, 4, offset_sb) self.offset_sbs.append(offset_sb) self.overlay_tw.setColumnWidth(0, 20) self.overlay_tw.setColumnWidth(1, 25) self.overlay_tw.setRowHeight(current_rows, 25) self.overlay_tw.horizontalHeader().setResizeMode( 3, QtWidgets.QHeaderView.ResizeToContents) self.overlay_tw.horizontalHeader().setResizeMode( 4, QtWidgets.QHeaderView.ResizeToContents) self.select_overlay(current_rows) self.overlay_tw.blockSignals(False) def select_overlay(self, ind): if self.overlay_tw.rowCount() > 0: self.overlay_tw.selectRow(ind) def get_selected_overlay_row(self): selected = self.overlay_tw.selectionModel().selectedRows() try: row = selected[0].row() except IndexError: row = -1 return row def remove_overlay(self, ind): self.overlay_tw.blockSignals(True) self.overlay_tw.removeRow(ind) self.overlay_tw.blockSignals(False) del self.show_cbs[ind] del self.color_btns[ind] del self.offset_sbs[ind] del self.scale_sbs[ind] if self.overlay_tw.rowCount() > ind: self.select_overlay(ind) else: self.select_overlay(self.overlay_tw.rowCount() - 1) def move_overlay_up(self, ind): new_ind = ind - 1 self.overlay_tw.blockSignals(True) self.overlay_tw.insertRow(new_ind) self.overlay_tw.setCellWidget(new_ind, 0, self.overlay_tw.cellWidget(ind + 1, 0)) self.overlay_tw.setCellWidget(new_ind, 1, self.overlay_tw.cellWidget(ind + 1, 1)) self.overlay_tw.setCellWidget(new_ind, 3, self.overlay_tw.cellWidget(ind + 1, 3)) self.overlay_tw.setCellWidget(new_ind, 4, self.overlay_tw.cellWidget(ind + 1, 4)) self.overlay_tw.setItem(new_ind, 2, self.overlay_tw.takeItem(ind + 1, 2)) self.overlay_tw.setCurrentCell(new_ind, 2) self.overlay_tw.removeRow(ind + 1) self.overlay_tw.setRowHeight(new_ind, 25) self.overlay_tw.blockSignals(False) self.color_btns.insert(new_ind, self.color_btns.pop(ind)) self.show_cbs.insert(new_ind, self.show_cbs.pop(ind)) self.scale_sbs.insert(new_ind, self.scale_sbs.pop(ind)) self.offset_sbs.insert(new_ind, self.offset_sbs.pop(ind)) def move_overlay_down(self, ind): new_ind = ind + 2 self.overlay_tw.blockSignals(True) self.overlay_tw.insertRow(new_ind) self.overlay_tw.setCellWidget(new_ind, 0, self.overlay_tw.cellWidget(ind, 0)) self.overlay_tw.setCellWidget(new_ind, 1, self.overlay_tw.cellWidget(ind, 1)) self.overlay_tw.setCellWidget(new_ind, 3, self.overlay_tw.cellWidget(ind, 3)) self.overlay_tw.setCellWidget(new_ind, 4, self.overlay_tw.cellWidget(ind, 4)) self.overlay_tw.setItem(new_ind, 2, self.overlay_tw.takeItem(ind, 2)) self.overlay_tw.setCurrentCell(new_ind, 2) self.overlay_tw.setRowHeight(new_ind, 25) self.overlay_tw.removeRow(ind) self.overlay_tw.blockSignals(False) self.color_btns.insert(ind + 1, self.color_btns.pop(ind)) self.show_cbs.insert(ind + 1, self.show_cbs.pop(ind)) self.scale_sbs.insert(ind + 1, self.scale_sbs.pop(ind)) self.offset_sbs.insert(ind + 1, self.offset_sbs.pop(ind)) def color_btn_click(self, button): self.color_btn_clicked.emit(self.color_btns.index(button), button) def show_cb_changed(self, checkbox): self.show_cb_state_changed.emit(self.show_cbs.index(checkbox), checkbox.isChecked()) def show_cb_set_checked(self, ind, state): checkbox = self.show_cbs[ind] checkbox.setChecked(state) def show_cb_is_checked(self, ind): checkbox = self.show_cbs[ind] return checkbox.isChecked() def label_editingFinished(self, row, col): label_item = self.overlay_tw.item(row, col) self.name_changed.emit(row, str(label_item.text())) def scale_sb_callback(self, scale_sb): self.scale_sb_value_changed.emit(self.scale_sbs.index(scale_sb), scale_sb.value()) def offset_sb_callback(self, offset_sb): self.offset_sb_value_changed.emit(self.offset_sbs.index(offset_sb), offset_sb.value())
class LoggedQuantity(QtCore.QObject): """ **LoggedQuantity** objects are containers that wrap settings. These settings may be a number (integer or float) or a string and occasionally small arrays of them. These objects emit signals when changed and can be connected bidirectionally to Qt Widgets. In ScopeFoundry we represent the values in an object called a `LoggedQuantity`. A :class:`LoggedQuantity` is a class that contains a value, a `bool`, `float`, `int`, `str` etc that is part of an application's state. In the case of microscope and equipment control, these also can represent the state of a piece of hardware. These are very useful objects because the are the central location of the value contained within. All graphical interface views will be guaranteed to be consistent with the `LQ` state. The data of these quantities will also be saved in datafiles created by ScopeFoundry. """ # signal sent when value has been updated updated_value = QtCore.Signal((float,),(int,),(bool,), (), (str,),) # signal sent when value has been updated, sends text representation updated_text_value = QtCore.Signal(str) # emits the index of the value in self.choices updated_choice_index_value = QtCore.Signal(int) # signal sent when min max range updated updated_min_max = QtCore.Signal((float,float),(int,int), (),) # signal sent when read only (ro) status has changed updated_readonly = QtCore.Signal((bool,), (),) def __init__(self, name, dtype=float, hardware_read_func=None, hardware_set_func=None, initial=0, fmt="%g", si=False, ro = False, # read only flag unit = None, spinbox_decimals = 2, spinbox_step=0.1, vmin=-1e12, vmax=+1e12, choices=None, reread_from_hardware_after_write = False, description = None ): QtCore.QObject.__init__(self) self.name = name self.dtype = dtype self.val = dtype(initial) self.hardware_read_func = hardware_read_func self.hardware_set_func = hardware_set_func self.fmt = fmt # string formatting string. This is ignored if dtype==str if self.dtype == str: self.fmt = "%s" self.si = si # will use pyqtgraph SI Spinbox if True self.unit = unit self.vmin = vmin self.vmax = vmax # choices should be tuple [ ('name', val) ... ] or simple list [val, val, ...] self.choices = self._expand_choices(choices) self.ro = ro # Read-Only self.is_array = False self.description = description self.log = get_logger_from_class(self) if self.dtype == int: self.spinbox_decimals = 0 else: self.spinbox_decimals = spinbox_decimals self.reread_from_hardware_after_write = reread_from_hardware_after_write if self.dtype == int: self.spinbox_step = 1 else: self.spinbox_step = spinbox_step self.oldval = None self._in_reread_loop = False # flag to prevent reread from hardware loops self.widget_list = [] self.listeners = [] # threading lock #self.lock = threading.Lock() #self.lock = DummyLock() self.lock = QLock(mode=0) # mode 0 is non-reentrant lock def coerce_to_type(self, x): """ Force x to dtype of the LQ ============= ================================== **Arguments** **Description** *x* value of type str, bool, int, etc. ============= ================================== :returns: Same value, *x* of the same type as its respective logged quantity """ if self.dtype==bool and isinstance(x, str): return str2bool(x) return self.dtype(x) def _expand_choices(self, choices): if choices is None: return None expanded_choices = [] for c in choices: if isinstance(c, tuple): name, val = c expanded_choices.append( ( str(name), self.dtype(val) ) ) else: expanded_choices.append( ( str(c), self.dtype(c) ) ) return expanded_choices def __str__(self): return "{} = {}".format(self.name, self.val) def __repr__(self): return "LQ: {} = {}".format(self.name, self.val) def read_from_hardware(self, send_signal=True): self.log.debug("{}: read_from_hardware send_signal={}".format(self.name, send_signal)) if self.hardware_read_func: with self.lock: self.oldval = self.val val = self.hardware_read_func() self.update_value(new_val=val, update_hardware=False, send_signal=send_signal) else: self.log.warn("{} read_from_hardware called when not connected to hardware".format(self.name)) return self.val def write_to_hardware(self, reread_hardware=None): if reread_hardware is None: # if undefined, default to stored reread_from_hardware_after_write bool reread_hardware = self.reread_from_hardware_after_write # Read from Hardware if self.has_hardware_write(): with self.lock: self.hardware_set_func(self.val) if reread_hardware: self.read_from_hardware(send_signal=False) def value(self): "return stored value" return self.val @QtCore.Slot(str) @QtCore.Slot(float) @QtCore.Slot(int) @QtCore.Slot(bool) @QtCore.Slot() def update_value(self, new_val=None, update_hardware=True, send_signal=True, reread_hardware=None): """ Update stored value with new_val Change value of LQ and emit signals to inform listeners of change if *update_hardware* is true: call connected hardware write function =============== ================================================================================================================= **Arguments:** **Description:** new_val New value for the LoggedQuantity to store update_hardware calls hardware_set_func if defined (default True) send_signal sends out QT signals on upon change (default True) reread_hardware read from hardware after writing to hardware to ensure change (defaults to self.reread_from_hardware_after_write) =============== ================================================================================================================= :returns: None """ # use a thread lock during update_value to avoid another thread # calling update_value during the update_value if reread_hardware is None: # if undefined, default to stored reread_from_hardware_after_write bool reread_hardware = self.reread_from_hardware_after_write with self.lock: # sometimes a the sender is a textbox that does not send its new value, # grab the text() from it instead if new_val is None: new_val = self.sender().text() self.oldval = self.coerce_to_type(self.val) new_val = self.coerce_to_type(new_val) self.log.debug("{}: update_value {} --> {} sender={}".format(self.name, self.oldval, new_val, self.sender())) # check for equality of new vs old, do not proceed if they are same if self.same_values(self.oldval, new_val): self.log.debug("{}: same_value so returning {} {}".format(self.name, self.oldval, new_val)) return else: pass # actually change internal state value self.val = new_val # Read from Hardware if update_hardware and self.hardware_set_func: self.hardware_set_func(self.val) if reread_hardware: self.read_from_hardware(send_signal=False) # Send Qt Signals if send_signal: self.send_display_updates() def send_display_updates(self, force=False): """ Emit updated_value signals if value has changed. ============= ============================================= **Arguments** **Description** *force* will emit signals regardless of value change. ============= ============================================= :returns: None """ #self.log.debug("send_display_updates: {} force={}".format(self.name, force)) if (not self.same_values(self.oldval, self.val)) or (force): self.updated_value[()].emit() str_val = self.string_value() self.updated_value[str].emit(str_val) self.updated_text_value.emit(str_val) if self.dtype in [float, int]: self.updated_value[float].emit(self.val) self.updated_value[int].emit(int(self.val)) self.updated_value[bool].emit(bool(self.val)) if self.choices is not None: choice_vals = [c[1] for c in self.choices] if self.val in choice_vals: self.updated_choice_index_value.emit(choice_vals.index(self.val) ) self.oldval = self.val else: # no updates sent pass def same_values(self, v1, v2): """ Compares two values of the LQ type, used in update_value ============= ==================== **Arguments** **Description** v1 value 1 v2 value 2 ============= ==================== :returns: Boolean value (True or False) """ return v1 == v2 def string_value(self): if self.dtype == str: return self.val else: return self.fmt % self.val def ini_string_value(self): """ :returns: A string showing the logged quantity value. """ return str(self.val) def update_choice_index_value(self, new_choice_index, **kwargs): self.update_value(self.choices[new_choice_index][1], **kwargs) def add_listener(self, func, argtype=(), **kwargs): """ Connect 'func' as a listener (Qt Slot) for the updated_value signal. By default 'func' should take no arguments, but argtype can define the data type that it should accept. but should be limited to those supported by LoggedQuantity (i.e. int, float, str) \*\*kwargs are passed to the connect function appends the 'func' to the 'listeners' list """ # --> This is now handled by redefining sys.excepthook handle in ScopeFoundry.base_app # Wraps func in a try block to absorb the Exception to avoid crashing PyQt5 >5.5 # see https://riverbankcomputing.com/pipermail/pyqt/2016-March/037134.html # def wrapped_func(func): # def f(*args): # try: # func(*args) # except Exception as err: # print "Exception on listener:" self.updated_value[argtype].connect(func, **kwargs) self.listeners.append(func) def connect_bidir_to_widget(self, widget): # DEPRECATED return self.connect_to_widget(widget) def connect_to_widget(self, widget): """ Creates Qt signal-slot connections between LQ and the QtWidget *widget* connects updated_value signal to the appropriate slot depending on the type of widget Makes a bidirectional connection to a QT widget, ie when LQ is updated, widget gets a signal and when widget is updated, the LQ receives a signal and update_value() slot is called. Handles many types of widgets: * QDoubleSpinBox * QCheckBox * QLineEdit * QComboBox * pyqtgraph.widgets.SpinBox.SpinBox ============= ==================================================================== **Arguments** **Description** widget The name of the Qt GUI Object, examples of which are listed above. For example, if you have a QDoubleSpinBox in the gui which you renamed int_value_doubleSpinBox in the Qt Designer Object Inspector, use self.ui.int_value_doubleSpinBox ============= ==================================================================== :returns: None """ if type(widget) == QtWidgets.QDoubleSpinBox: widget.setKeyboardTracking(False) if self.vmin is not None: widget.setMinimum(self.vmin) if self.vmax is not None: widget.setMaximum(self.vmax) if self.unit is not None: widget.setSuffix(" "+self.unit) widget.setDecimals(self.spinbox_decimals) widget.setSingleStep(self.spinbox_step) widget.setValue(self.val) #events def update_widget_value(x): """ block signals from widget when value is set via lq.update_value. This prevents signal-slot loops between widget and lq """ try: widget.blockSignals(True) widget.setValue(x) finally: widget.blockSignals(False) #self.updated_value[float].connect(widget.setValue) self.updated_value[float].connect(update_widget_value) #if not self.ro: widget.valueChanged[float].connect(self.update_value) elif type(widget) == QtWidgets.QSlider: self.vrange = self.vmax - self.vmin def transform_to_slider(x): pct = 100*(x-self.vmin)/self.vrange return int(pct) def transform_from_slider(x): val = self.vmin + (x*self.vrange/100) return val def update_widget_value(x): """ block signals from widget when value is set via lq.update_value. This prevents signal-slot loops between widget and lq """ try: widget.blockSignals(True) widget.setValue(transform_to_slider(x)) finally: widget.blockSignals(False) def update_spinbox(x): self.update_value(transform_from_slider(x)) if self.vmin is not None: widget.setMinimum(transform_to_slider(self.vmin)) if self.vmax is not None: widget.setMaximum(transform_to_slider(self.vmax)) widget.setSingleStep(1) widget.setValue(transform_to_slider(self.val)) self.updated_value[float].connect(update_widget_value) widget.valueChanged[int].connect(update_spinbox) elif type(widget) == QtWidgets.QCheckBox: def update_widget_value(x): try: widget.blockSignals(True) widget.setChecked(x) finally: widget.blockSignals(False) self.updated_value[bool].connect(update_widget_value) widget.toggled[bool].connect(self.update_value) # another option is stateChanged signal if self.ro: #widget.setReadOnly(True) widget.setEnabled(False) elif type(widget) == QtWidgets.QLineEdit: self.updated_text_value[str].connect(widget.setText) if self.ro: widget.setReadOnly(True) # FIXME def on_edit_finished(): self.log.debug(self.name + " qLineEdit on_edit_finished") try: widget.blockSignals(True) self.update_value(widget.text()) finally: widget.blockSignals(False) widget.editingFinished.connect(on_edit_finished) elif type(widget) == QtWidgets.QPlainTextEdit: self.updated_text_value[str].connect(widget.document().setPlainText) # TODO Read only def on_widget_textChanged(): try: widget.blockSignals(True) self.update_value(widget.toPlainText()) finally: widget.blockSignals(False) widget.textChanged.connect(on_widget_textChanged) elif type(widget) == QtWidgets.QComboBox: # need to have a choice list to connect to a QComboBox assert self.choices is not None widget.clear() # removes all old choices for choice_name, choice_value in self.choices: widget.addItem(choice_name, choice_value) self.updated_choice_index_value[int].connect(widget.setCurrentIndex) widget.currentIndexChanged.connect(self.update_choice_index_value) elif type(widget) == pyqtgraph.widgets.SpinBox.SpinBox: #widget.setFocusPolicy(QtCore.Qt.StrongFocus) suffix = self.unit if self.unit is None: suffix = "" if self.dtype == int: integer = True minStep=1 step=1 else: integer = False minStep=.1 step=.1 opts = dict( suffix=suffix, siPrefix=True, dec=True, step=step, minStep=minStep, bounds=[self.vmin, self.vmax], int=integer) if self.si: del opts['step'] del opts['minStep'] widget.setOpts(**opts) if self.ro: widget.setEnabled(False) widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) widget.setReadOnly(True) #widget.setDecimals(self.spinbox_decimals) if not self.si: widget.setSingleStep(self.spinbox_step) #self.updated_value[float].connect(widget.setValue) #if not self.ro: #widget.valueChanged[float].connect(self.update_value) def update_widget_value(x): """ block signals from widget when value is set via lq.update_value. This prevents signal loops """ try: widget.blockSignals(True) widget.setValue(x) finally: widget.blockSignals(False) self.updated_value[float].connect(update_widget_value) def on_widget_update(_widget): self.update_value(_widget.value()) widget.sigValueChanged.connect(on_widget_update) elif type(widget) == QtWidgets.QLabel: self.updated_text_value.connect(widget.setText) elif type(widget) == QtWidgets.QProgressBar: def set_progressbar(x, widget=widget): self.log.debug("set_progressbar {}".format(x)) widget.setValue(int(x)) self.updated_value.connect(set_progressbar) else: raise ValueError("Unknown widget type") self.send_display_updates(force=True) #self.widget = widget self.widget_list.append(widget) self.change_readonly(self.ro) def connect_to_lq(self, lq): self.updated_value.connect(lq.update_value) lq.updated_value.connect(self.update_value) def change_choice_list(self, choices): #widget = self.widget with self.lock: self.choices = self._expand_choices(choices) for widget in self.widget_list: if type(widget) == QtWidgets.QComboBox: # need to have a choice list to connect to a QComboBox assert self.choices is not None widget.clear() # removes all old choices for choice_name, choice_value in self.choices: widget.addItem(choice_name, choice_value) else: raise RuntimeError("Invalid widget type.") def change_min_max(self, vmin=-1e12, vmax=+1e12): # TODO setRange should be a slot for the updated_min_max signal with self.lock: self.vmin = vmin self.vmax = vmax for widget in self.widget_list: # may not work for certain widget types widget.setRange(vmin, vmax) self.updated_min_max.emit(vmin,vmax) def change_readonly(self, ro=True): with self.lock: self.ro = ro for widget in self.widget_list: if type(widget) in [QtWidgets.QDoubleSpinBox, pyqtgraph.widgets.SpinBox.SpinBox]: widget.setReadOnly(self.ro) #TODO other widget types self.updated_readonly.emit(self.ro) def change_unit(self, unit): with self.lock: self.unit = unit for widget in self.widget_list: if type(widget) == QtWidgets.QDoubleSpinBox: if self.unit is not None: widget.setSuffix(" "+self.unit) elif type(widget) == pyqtgraph.widgets.SpinBox.SpinBox: #widget.setFocusPolicy(QtCore.Qt.StrongFocus) suffix = self.unit if self.unit is None: suffix = "" opts = dict( suffix=suffix) widget.setOpts(**opts) def is_connected_to_hardware(self): """ :returns: True if either self.hardware_read_func or self.hardware_set_func are defined. False if None. """ return (self.hardware_read_func is not None) or (self.hardware_set_func is not None) def has_hardware_read(self): return self.hardware_read_func is not None def has_hardware_write(self): return self.hardware_set_func is not None def connect_to_hardware(self, read_func=None, write_func=None): if read_func is not None: assert callable(read_func) self.hardware_read_func = read_func if write_func is not None: assert callable(write_func) self.hardware_set_func = write_func def disconnect_from_hardware(self, dis_read=True, dis_write=True): if dis_read: self.hardware_read_func = None if dis_write: self.hardware_set_func = None
class InterruptableTask(QtCore.QObject, Fysom, metaclass=TaskMetaclass): """ This class represents a task in a module that can be safely executed by checking preconditions and pausing other tasks that are being executed as well. The task can also be paused, given that the preconditions for pausing are met. State diagram for InterruptableTask: stopped -> starting -----------> running ---------> finishing -* ^ | _______| ^_________ | |<---------* v | v | pausing -> paused -> resuming | | | | | ^ v v | |-------------<--------|----------<---------|--------<------- Each state has a transition state that allow for checks, synchronizatuion and for parts of the task to influence its own execution via signals. This also allows the TaskRunner to be informed about what the task is doing and ensuring that a task is executed in the correct thread. """ sigDoStart = QtCore.Signal() sigStarted = QtCore.Signal() sigNextTaskStep = QtCore.Signal() sigDoPause = QtCore.Signal() sigPaused = QtCore.Signal() sigDoResume = QtCore.Signal() sigResumed = QtCore.Signal() sigDoFinish = QtCore.Signal() sigFinished = QtCore.Signal() sigStateChanged = QtCore.Signal(object) prePostTasks = {} pauseTasks = {} requiredModules = [] def __init__(self, name, runner, references, config, **kwargs): """ Create an Interruptable task. @param str name: unique task name @param object runner: reference to the TaskRunner managing this task @param dict references: a dictionary of all required modules @param dict config: configuration dictionary """ default_callbacks = { 'onrun': self._start, 'onpause': self._pause, 'onresume': self._resume, 'onfinish': self._finish } _stateDict = { 'initial': 'stopped', 'events': [{ 'name': 'run', 'src': 'stopped', 'dst': 'starting' }, { 'name': 'startingFinished', 'src': 'starting', 'dst': 'running' }, { 'name': 'pause', 'src': 'running', 'dst': 'pausing' }, { 'name': 'pausingFinished', 'src': 'pausing', 'dst': 'paused' }, { 'name': 'finish', 'src': 'running', 'dst': 'finishing' }, { 'name': 'finishingFinished', 'src': 'finishing', 'dst': 'stopped' }, { 'name': 'resume', 'src': 'paused', 'dst': 'resuming' }, { 'name': 'resumingFinished', 'src': 'resuming', 'dst': 'running' }, { 'name': 'abort', 'src': 'pausing', 'dst': 'stopped' }, { 'name': 'abort', 'src': 'starting', 'dst': 'stopped' }, { 'name': 'abort', 'src': 'resuming', 'dst': 'stopped' }], 'callbacks': default_callbacks } if 'PyQt5' in sys.modules: super().__init__(cfg=_stateDict, **kwargs) else: QtCore.QObject.__init__(self) Fysom.__init__(self, _stateDict) self.lock = Mutex() self.name = name self.interruptable = False self.success = False self.runner = runner self.ref = references self.config = config self.sigDoStart.connect(self._doStart, QtCore.Qt.QueuedConnection) self.sigDoPause.connect(self._doPause, QtCore.Qt.QueuedConnection) self.sigDoResume.connect(self._doResume, QtCore.Qt.QueuedConnection) self.sigDoFinish.connect(self._doFinish, QtCore.Qt.QueuedConnection) self.sigNextTaskStep.connect(self._doTaskStep, QtCore.Qt.QueuedConnection) @property def log(self): """ Returns a logger object """ return logging.getLogger("{0}.{1}".format(self.__module__, self.__class__.__name__)) def onchangestate(self, e): """ Fysom callback for state transition. @param object e: Fysom state transition description """ self.sigStateChanged.emit(e) def _start(self, e): """ @param object e: Fysom state transition description @return bool: True if task was started, False otherwise """ self.result = TaskResult() if self.checkStartPrerequisites(): #print('_run', QtCore.QThread.currentThreadId(), self.current) self.sigDoStart.emit() #print('_runemit', QtCore.QThread.currentThreadId(), self.current) return True else: return False def _doStart(self): """ Starting prerequisites were met, now do the actual start action. """ try: #print('dostart', QtCore.QThread.currentThreadId(), self.current) self.runner.pausePauseTasks(self) self.runner.preRunPPTasks(self) self.startTask() self.startingFinished() self.sigStarted.emit() self.sigNextTaskStep.emit() except Exception as e: self.log.exception('Exception during task {0}. {1}'.format( self.name, e)) self.result.update(None, False) def _doTaskStep(self): """ Check for state transitions to pause or stop and execute one step of the task work function. """ try: if self.runTaskStep(): if self.isstate('pausing') and self.checkPausePrerequisites(): self.sigDoPause.emit() elif self.isstate('finishing'): self.sigDoFinish.emit() else: self.sigNextTaskStep.emit() else: self.finish() self.sigDoFinish.emit() except Exception as e: self.log.exception('Exception during task step {0}. {1}'.format( self.name, e)) self.result.update(None, False) self.finish() self.sigDoFinish.emit() def _pause(self, e): """ This does nothing, it is up to the TaskRunner to check that pausing is allowed and triger the next step. """ pass def _doPause(self): """ Prerequisites for pausing were checked by Task runner and met, so execute the actual pausing action. """ try: self.pauseTask() self.runner.postRunPPTasks(self) self.pausingFinished() self.sigPaused.emit() except Exception as e: self.log.exception('Exception while pausing task {}. ' '{}'.format(self.name, e)) self.result.update(None, False) def _resume(self, e): """ Trigger resuming action. """ self.sigDoResume.emit() def _doResume(self): """ Actually execute resuming action. """ try: self.runner.preRunPPTasks(self) self.resumeTask() self.resumingFinished() self.sigResumed.emit() self.sigNextTaskStep.emit() except Exception as e: self.log.exception('Exception while resuming task {}. ' '{}'.format(self.name, e)) self.result.update(None, False) def _finish(self, e): """ Do nothing, it is up to the TaskRunner to trigger the next step. """ pass def _doFinish(self): """ Actually finish execution. """ self.cleanupTask() self.runner.resumePauseTasks(self) self.runner.postRunPPTasks(self) self.finishingFinished() self.sigFinished.emit() def checkStartPrerequisites(self): """ Check whether this task can be started by checking if all tasks to be paused are either stopped or can be paused. Also check custom prerequisites. @return bool: True if task can be stated, False otherwise """ for task in self.prePostTasks: if not (isinstance(self.prePostTasks[task], PrePostTask) and self.prePostTasks[task].can('prerun')): self.log( 'Cannot start task {0} as pre/post task {1} is not in a state to run.' .format(self.name, task), msgType='error') return False for task in self.pauseTasks: if not (isinstance(self.pauseTasks[task], InterruptableTask) and (self.pauseTasks[task].can('pause') or self.pauseTasks[task].isstate('stopped'))): self.log( 'Cannot start task {0} as interruptable task {1} is not stopped or able to pause.' .format(self.name, task), msgType='error') return False if not self.checkExtraStartPrerequisites(): return False return True def checkExtraStartPrerequisites(self): """ If your task has extra prerequisites that are not covered by checking if a certain task can be paused, overwrite this function when sub-classing. @return bool: return True if task can be started, False otherwise """ return True def checkPausePrerequisites(self): """ Check if task is allowed to pause based on external state.""" try: return self.checkExtraPausePrerequisites() except Exception as e: self.log.exception('Exception while checking pause ' 'prerequisites for task {}. {}'.format( self.name, e)) return False def checkExtraPausePrerequisites(self): """ If yout task has prerequisites for pausing, overwrite this function when subclassing and put the check here. @return bool: return True if task can be paused right now, False otherwise """ return True def canPause(self): """ Check if task can pause based on its own state only. """ return self.interruptable and self.can( 'pause') and self.checkPausePrerequisites() @abc.abstractmethod def startTask(self): """ Implement the operation to start your task here. """ pass @abc.abstractmethod def runTaskStep(self): """ Implement one work step of your task here. @return bool: True if the task should continue running, False if it should finish. """ return False @abc.abstractmethod def pauseTask(self): """ Implement the operations necessary to pause your task here. """ pass @abc.abstractmethod def resumeTask(self): """ Implement the operations necessary to resume your task from being paused here. """ pass @abc.abstractmethod def cleanupTask(self): """ If your task leaves behind any undesired state, take care to remove it in this function. It is called after a task has finished. """ pass
class ArrayLQ(LoggedQuantity): updated_shape = QtCore.Signal(str) def __init__(self, name, dtype=float, hardware_read_func=None, hardware_set_func=None, initial=[], fmt="%g", si=True, ro = False, unit = None, vmin=-1e12, vmax=+1e12, choices=None): QtCore.QObject.__init__(self) self.name = name self.dtype = dtype if self.dtype == str: self.val = np.array(initial, dtype=object) else: self.val = np.array(initial, dtype=dtype) self.hardware_read_func = hardware_read_func self.hardware_set_func = hardware_set_func self.fmt = fmt # % string formatting string. This is ignored if dtype==str if self.dtype == str: self.fmt = "%s" self.unit = unit self.vmin = vmin self.vmax = vmax self.ro = ro # Read-Only self.log = get_logger_from_class(self) if self.dtype == int: self.spinbox_decimals = 0 else: self.spinbox_decimals = 2 self.reread_from_hardware_after_write = False self.oldval = None self._in_reread_loop = False # flag to prevent reread from hardware loops self.widget_list = [] self.listeners = [] # threading lock self.lock = QLock(mode=0) # mode 0 is non-reentrant lock self.is_array = True self._tableView = None def same_values(self, v1, v2): if v1.shape == v2.shape: return np.all(v1 == v2) self.log.debug("same_values %s %s" % (v2-v1, np.all(v1 == v2))) else: return False def change_shape(self, newshape): #TODO pass def string_value (self): return json.dumps(self.val.tolist()) def ini_string_value(self): return json.dumps(self.val.tolist()) def coerce_to_type(self, x): #print type(x) if type(x) in (unicode, str): x = json.loads(x) #print repr(x) return np.array(x, dtype=self.dtype) def send_display_updates(self, force=False): with self.lock: self.log.debug(self.name + ' send_display_updates') #print "send_display_updates: {} force={}".format(self.name, force) if force or np.any(self.oldval != self.val): #print "send display updates", self.name, self.val, self.oldval str_val = self.string_value() self.updated_value[str].emit(str_val) self.updated_text_value.emit(str_val) #self.updated_value[float].emit(self.val) #if self.dtype != float: # self.updated_value[int].emit(self.val) #self.updated_value[bool].emit(self.val) self.updated_value[()].emit() self.oldval = self.val else: pass #print "\t no updates sent", (self.oldval != self.val) , (force), self.oldval, self.val @property def array_tableView(self): if self._tableView == None: self._tableView = self.create_tableView() self._tableView.setWindowTitle(self.name) return self._tableView def create_tableView(self, **kwargs): widget = QtWidgets.QTableView() #widget.horizontalHeader().hide() #widget.verticalHeader().hide() model = ArrayLQ_QTableModel(self, transpose=(len(self.val.shape) == 1), **kwargs) widget.setModel(model) return widget
class MeasurementDialog(QtWidgets.QDialog): measurements_available = QtCore.Signal(object) def __init__(self, nwa, parent=None): super(MeasurementDialog, self).__init__(parent) self.setWindowTitle("MeasurementDialog") self.horizontalLayout_main = QtWidgets.QHBoxLayout(self) self.verticalLayout_left = QtWidgets.QVBoxLayout() self.groupBox_options = QtWidgets.QGroupBox("Options", self) self.lineEdit_namePrefix = QtWidgets.QLineEdit(self) self.label_namePrefix = QtWidgets.QLabel("Name Prefix:") self.horizontalLayout_namePrefix = QtWidgets.QHBoxLayout() self.horizontalLayout_namePrefix.addWidget(self.label_namePrefix) self.horizontalLayout_namePrefix.addWidget(self.lineEdit_namePrefix) self.label_timeout = QtWidgets.QLabel("Timeout (ms)", self) self.spinBox_timeout = QtWidgets.QSpinBox(self) self.spinBox_timeout.setMinimum(100) self.spinBox_timeout.setMaximum(600000) try: self.spinBox_timeout.setValue(nwa.resource.timeout) except: self.spinBox_timeout.setValue(3000) self.spinBox_timeout.setSingleStep(1000) self.horizontalLayout_timeout = QtWidgets.QHBoxLayout() self.horizontalLayout_timeout.addWidget(self.label_timeout) self.horizontalLayout_timeout.addWidget(self.spinBox_timeout) self.checkBox_sweepNew = QtWidgets.QCheckBox("Sweep New", self.groupBox_options) self.checkBox_autoTimeOut = QtWidgets.QCheckBox( "Auto Timeout", self.groupBox_options) self.horizonatlLayout_sweep = QtWidgets.QHBoxLayout() self.horizonatlLayout_sweep.addWidget(self.checkBox_sweepNew) self.horizonatlLayout_sweep.addWidget(self.checkBox_autoTimeOut) self.label_channel = QtWidgets.QLabel("Channel", self.groupBox_options) self.spinBox_channel = QtWidgets.QSpinBox(self.groupBox_options) self.horizontalLayout_channel = QtWidgets.QHBoxLayout() self.horizontalLayout_channel.addWidget(self.label_channel) self.horizontalLayout_channel.addWidget(self.spinBox_channel) self.verticalLayout_options = QtWidgets.QVBoxLayout( self.groupBox_options) self.verticalLayout_options.addLayout(self.horizontalLayout_namePrefix) self.verticalLayout_options.addLayout(self.horizontalLayout_timeout) self.verticalLayout_options.addLayout(self.horizonatlLayout_sweep) self.verticalLayout_options.addLayout(self.horizontalLayout_channel) self.verticalLayout_left.addWidget(self.groupBox_options) self.groupBox_snp = QtWidgets.QGroupBox("Get N-Port Network", self) self.verticalLayout_snp = QtWidgets.QVBoxLayout(self.groupBox_snp) self.label_ports = QtWidgets.QLabel("Ports:", self.groupBox_snp) self.lineEdit_ports = QtWidgets.QLineEdit(self.groupBox_snp) self.btn_measureSnp = QtWidgets.QPushButton("Measure Network", self.groupBox_snp) self.horizontalLayout_nports = QtWidgets.QHBoxLayout() self.horizontalLayout_nports.addWidget(self.label_ports) self.horizontalLayout_nports.addWidget(self.lineEdit_ports) self.verticalLayout_snp.addWidget(self.btn_measureSnp) self.verticalLayout_snp.addLayout(self.horizontalLayout_nports) self.spacerItem = QtWidgets.QSpacerItem( 20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_snp.addItem(self.spacerItem) self.verticalLayout_left.addWidget(self.groupBox_snp) self.groupBox_traces = QtWidgets.QGroupBox("Available Traces", self) self.listWidget_traces = QtWidgets.QListWidget(self.groupBox_traces) self.listWidget_traces.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) self.btn_updateTraces = QtWidgets.QPushButton("Update", self.groupBox_traces) self.btn_measureTraces = QtWidgets.QPushButton("Measure Traces", self.groupBox_traces) self.horizontalLayout_tracesButtons = QtWidgets.QHBoxLayout() self.horizontalLayout_tracesButtons.addWidget(self.btn_updateTraces) self.horizontalLayout_tracesButtons.addWidget(self.btn_measureTraces) self.verticalLayout_traces = QtWidgets.QVBoxLayout( self.groupBox_traces) self.verticalLayout_traces.addWidget(self.listWidget_traces) self.verticalLayout_traces.addLayout( self.horizontalLayout_tracesButtons) self.horizontalLayout_main.addLayout(self.verticalLayout_left) self.horizontalLayout_main.addWidget(self.groupBox_traces) self.nwa = nwa self.btn_updateTraces.clicked.connect(self.update_traces) self.btn_measureSnp.clicked.connect(self.measure_snp) self.btn_measureTraces.clicked.connect(self.measure_traces) if self.nwa.NCHANNELS: self.spinBox_channel.setValue(1) self.spinBox_channel.setMinimum(1) self.spinBox_channel.setMaximum(self.nwa.NCHANNELS) else: self.spinBox_channel.setEnabled(False) self.lineEdit_ports.setText(",".join( [str(port + 1) for port in range(self.nwa.nports)])) self.spinBox_timeout.valueChanged.connect(self.set_timeout) def set_timeout(self): self.nwa.resource.timeout = self.spinBox_timeout.value() def measure_traces(self): items = self.listWidget_traces.selectedItems() if len(items) < 1: print("nothing to measure") return traces = [] for item in items: traces.append(item.trace) ntwks = self.nwa.get_traces( traces, name_prefix=self.lineEdit_namePrefix.text()) self.measurements_available.emit(ntwks) def measure_snp(self): ports = self.lineEdit_ports.text().replace(" ", "").split(",") try: ports = [int(port) for port in ports] except Exception: qt.error_popup("Ports must be a comma separated list of integers") return kwargs = { "ports": ports, "channel": self.spinBox_channel.value(), "sweep": self.checkBox_sweepNew.isChecked(), "name": self.lineEdit_namePrefix.text() } if self.checkBox_autoTimeOut.isChecked(): kwargs["timeout"] = self.spinBox_timeout.value() ntwk = self.nwa.get_snp_network(**kwargs) self.measurements_available.emit(ntwk) def update_traces(self): traces = self.nwa.get_list_of_traces() self.listWidget_traces.clear() for trace in traces: item = QtWidgets.QListWidgetItem() item.setText(trace["label"]) item.trace = trace self.listWidget_traces.addItem(item)
class LQRange(QtCore.QObject): """ LQRange is a collection of logged quantities that describe a numpy.linspace array inputs Four LQ's are defined, min, max, num, step and are connected by signals/slots that keep the quantities in sync. LQRange.array is the linspace array and is kept upto date with changes to the 4 LQ's """ updated_range = QtCore.Signal((),)# (float,),(int,),(bool,), (), (str,),) # signal sent when value has been updated def __init__(self, min_lq,max_lq,step_lq, num_lq, center_lq=None, span_lq=None): QtCore.QObject.__init__(self) self.log = get_logger_from_class(self) self.min = min_lq self.max = max_lq self.num = num_lq self.step = step_lq self.center = center_lq self.span = span_lq assert self.num.dtype == int self._array_valid = False # Internal _array invalid, must be computed on next request self._array = None #np.linspace(self.min.val, self.max.val, self.num.val) #step = self._array[1]-self._array[0] step = self.compute_step(self.min.val, self.max.val, self.num.val) self.step.update_value(step) self.num.updated_value[int].connect(self.recalc_with_new_num) self.min.updated_value.connect(self.recalc_with_new_min_max) self.max.updated_value.connect(self.recalc_with_new_min_max) self.step.updated_value.connect(self.recalc_with_new_step) if self.center and self.span: self.center.updated_value.connect(self.recalc_with_new_center_span) self.span.updated_value.connect(self.recalc_with_new_center_span) @property def array(self): if self._array_valid: return self._array else: self._array = np.linspace(self.min.val, self.max.val, self.num.val) self._array_valid = True return self._array def compute_step(self, xmin, xmax, num): delta = xmax - xmin if num > 1: return delta/(num-1) else: return delta def recalc_with_new_num(self, new_num): self.log.debug("recalc_with_new_num {}".format( new_num)) self._array_valid = False self._array = None #self._array = np.linspace(self.min.val, self.max.val, int(new_num)) new_step = self.compute_step(self.min.val, self.max.val, int(new_num)) self.log.debug( " new_step inside new_num {}".format( new_step)) self.step.update_value(new_step)#, send_signal=True, update_hardware=False) self.step.send_display_updates(force=True) self.updated_range.emit() def recalc_with_new_min_max(self, x): self._array_valid = False self._array = None #self._array = np.linspace(self.min.val, self.max.val, self.num.val) #step = self._array[1]-self._array[0] step = self.compute_step(self.min.val, self.max.val, self.num.val) self.step.update_value(step)#, send_signal=True, update_hardware=False) if self.center: self.span.update_value(0.5*(self.max.val-self.min.val) + self.min.val) if self.span: self.span.update_value(self.max.val-self.min.val) self.updated_range.emit() def recalc_with_new_step(self,new_step): #print "-->recalc_with_new_step" if self.num.val > 1: #old_step = self._array[1]-self._array[0] old_step = self.compute_step(self.min.val, self.max.val, self.num.val) else: old_step = np.nan sdiff = np.abs(old_step - new_step) #print "step diff", sdiff if sdiff < 10**(-self.step.spinbox_decimals): #print "steps close enough, no more recalc" return else: self._array_valid = False self._array = None new_num = int((((self.max.val - self.min.val)/new_step)+1)) #self._array = np.linspace(self.min.val, self.max.val, new_num) #new_step1 = self._array[1]-self._array[0] new_step1 = self.compute_step(self.min.val, self.max.val, new_num) #print "recalc_with_new_step", new_step, new_num, new_step1 #self.step.val = new_step1 #self.num.val = new_num #self.step.update_value(new_step1, send_signal=False) #if np.abs(self.step.val - new_step1)/self.step.val > 1e-2: self.step.val = new_step1 self.num.update_value(new_num) #self.num.send_display_updates(force=True) #self.step.update_value(new_step1) #print "sending step display Updates" #self.step.send_display_updates(force=True) self.updated_range.emit() def recalc_with_new_center_span(self,x): C = self.center.val S = self.span.val self.min.update_value( C - 0.5*S) self.max.update_value( C + 0.5*S)
class ResultsTabView(QtWidgets.QWidget, ui_fitting_tab): # Public signals function_selection_changed = QtCore.Signal() results_name_edited = QtCore.Signal() output_results_requested = QtCore.Signal() def __init__(self, parent=None): super(ResultsTabView, self).__init__(parent) self._init_layout() self._init_signals() def set_output_results_button_enabled(self, on): """ Set the status of the output results button :param on: If True then enable the button otherwise disable it """ self.output_results_table_btn.setEnabled(on) def results_table_name(self): """Return the name of the output table.""" return self.results_name_editor.text() def set_results_table_name(self, name): """Set the name of the output table. :param name: A new name for the table """ self.results_name_editor.setText(name) def set_fit_function_names(self, names): """Set a new list of function names for the function selector. This blocks signals from the widget during the update :param names: A list of strings specifying function names used in known fits """ function_selector = self.fit_function_selector original_selection = function_selector.currentText() function_selector.blockSignals(True) function_selector.clear() function_selector.addItems(names) if original_selection in names: self.set_selected_fit_function(original_selection) function_selector.blockSignals(False) def selected_result_workspaces(self): """ :return: The list of selected workspaces and their positions in the list """ return self.fit_selector_presenter.get_selected_items_and_positions() def fit_result_workspaces(self): """ :return: The current state of the workspace list selector """ return self.fit_selector_presenter.model def set_fit_result_workspaces(self, workspace_list_state): """Set the map of workspaces :param workspace_list_state: Dictionary containing the updated state for the workspace list selector """ self.fit_selector_presenter.update_model(workspace_list_state) def set_selected_fit_function(self, function): """ Set the fit function in the QComboBox """ if PYQT4: self.fit_function_selector.setCurrentIndex( self.fit_function_selector.findText(function)) else: self.fit_function_selector.setCurrentText(function) def selected_fit_function(self): """Return the text of the selected item in the function selection box""" return self.fit_function_selector.currentText() def selected_log_values(self): """ :return: A list of selected log values and their positions in the list """ return self.log_selector_presenter.get_selected_items() def log_values(self): """ :return: The current state of the log list selector """ return self.log_selector_presenter.model def set_log_values(self, logs_list_state): """Set the map of log values and selected status :param logs_list_state: Dictionary containing the updated state for the workspace list selector """ self.log_selector_presenter.update_model(logs_list_state) def show_warning(self, msg): """ Display a warning on that something went wrong :param msg: The message to include """ warning(msg, self) # Private methods def _init_layout(self): """Setup the layout of the view""" self.setupUi(self) self.log_selector_presenter = _create_empty_list_selector( self, LOG_SELECTOR_COL0_WIDTH) self.log_value_layout.addWidget(self.log_selector_presenter.view, *FIT_SELECTOR_GRID_POS) self.fit_selector_presenter = _create_empty_list_selector( self, FIT_SELECTOR_COL0_WIDTH) self.fit_layout.addWidget(self.fit_selector_presenter.view, *LOG_SELECTOR_GRID_POS) def _init_signals(self): """Connect internal signals to external notifiers""" self.fit_function_selector.currentIndexChanged.connect( self.function_selection_changed) self.results_name_editor.editingFinished.connect( self.results_name_edited) self.output_results_table_btn.clicked.connect( self.output_results_requested)
class Parameter(QtCore.QObject): changed = QtCore.Signal(object) def __init__(self, name=None, label=None, label_above=False, **kwargs): super().__init__() self.name = name self.label = label self._label_above = label_above self.__options = kwargs if kwargs else {} if _have_qt: self._createWithLabel() def _attachTo(self, obj): if self.name: obj._par_name_dict[self.name] = self def setVisible(self, val): if _have_qt: self._widget._visible = val self._widget.setVisible(val) def isVisible(self): return self._widget._visible if hasattr(self._widget, "_visible") else True def _createWithLabel(self): if self.label: self._widget = QtWidgets.QWidget() arrange = ArrangeV if self._label_above else ArrangeH layout = arrange(QtWidgets.QLabel(self.label), self._createWidget()) layout.setContentsMargins(0, 0, 0, 0) self._widget.setLayout(layout) else: self._widget = self._createWidget() def _createWidget(self): raise NotImplementedError( "Parameter class must overload _createWidget!") def getOption(self, name): if not name in self.__options: return False else: return self.__options[name] def setValue(self, val=None): self.changed.emit(val) def getWidget(self): return self._widget def __getstate__(self): return (self.name, self.label, self.__options, self._label_above) def __setstate__(self, state): Parameter.__init__(self, name=state[0], label=state[1], label_above=state[3]) self.__options = state[2]
class Canvas(QtWidgets.QWidget): zoomRequest = QtCore.Signal(int, QtCore.QPoint) scrollRequest = QtCore.Signal(int, int) newShape = QtCore.Signal() selectionChanged = QtCore.Signal(bool) shapeMoved = QtCore.Signal() drawingPolygon = QtCore.Signal(bool) edgeSelected = QtCore.Signal(bool) CREATE, EDIT = 0, 1 # polygon, rectangle, line, or point _createMode = 'polygon' _fill_drawing = False def __init__(self, *args, **kwargs): self.epsilon = kwargs.pop('epsilon', 10.0) super(Canvas, self).__init__(*args, **kwargs) # Initialise local state. self.mode = self.EDIT self.shapes = [] self.shapesBackups = [] self.current = None self.selectedShape = None # save the selected shape here self.selectedShapeCopy = None self.lineColor = QtGui.QColor(0, 0, 255) # self.line represents: # - createMode == 'polygon': edge from last point to current # - createMode == 'rectangle': diagonal line of the rectangle # - createMode == 'line': the line # - createMode == 'point': the point self.line = Shape(line_color=self.lineColor) self.prevPoint = QtCore.QPoint() self.prevMovePoint = QtCore.QPoint() self.offsets = QtCore.QPoint(), QtCore.QPoint() self.scale = 1.0 self.pixmap = QtGui.QPixmap() self.visible = {} self._hideBackround = False self.hideBackround = False self.hShape = None self.hVertex = None self.hEdge = None self.movingShape = False self._painter = QtGui.QPainter() self._cursor = CURSOR_DEFAULT # Menus: self.menus = (QtWidgets.QMenu(), QtWidgets.QMenu()) # Set widget options. self.setMouseTracking(True) self.setFocusPolicy(QtCore.Qt.WheelFocus) def fillDrawing(self): return self._fill_drawing def setFillDrawing(self, value): self._fill_drawing = value @property def createMode(self): return self._createMode @createMode.setter def createMode(self, value): if value not in ['polygon', 'rectangle', 'circle', 'line', 'point', 'linestrip']: raise ValueError('Unsupported createMode: %s' % value) self._createMode = value def storeShapes(self): shapesBackup = [] for shape in self.shapes: shapesBackup.append(shape.copy()) if len(self.shapesBackups) >= 10: self.shapesBackups = self.shapesBackups[-9:] self.shapesBackups.append(shapesBackup) @property def isShapeRestorable(self): if len(self.shapesBackups) < 2: return False return True def restoreShape(self): if not self.isShapeRestorable: return self.shapesBackups.pop() # latest shapesBackup = self.shapesBackups.pop() self.shapes = shapesBackup self.storeShapes() self.repaint() def enterEvent(self, ev): self.overrideCursor(self._cursor) def leaveEvent(self, ev): self.restoreCursor() def focusOutEvent(self, ev): self.restoreCursor() def isVisible(self, shape): return self.visible.get(shape, True) def drawing(self): return self.mode == self.CREATE def editing(self): return self.mode == self.EDIT def setEditing(self, value=True): self.mode = self.EDIT if value else self.CREATE if not value: # Create self.unHighlight() self.deSelectShape() def unHighlight(self): if self.hShape: self.hShape.highlightClear() self.hVertex = self.hShape = None def selectedVertex(self): return self.hVertex is not None def mouseMoveEvent(self, ev): """Update line with last point and current coordinates.""" try: if QT5: pos = self.transformPos(ev.pos()) else: pos = self.transformPos(ev.posF()) except AttributeError: return self.prevMovePoint = pos self.restoreCursor() # Polygon drawing. if self.drawing(): self.line.shape_type = self.createMode self.overrideCursor(CURSOR_DRAW) if not self.current: return color = self.lineColor if self.outOfPixmap(pos): # Don't allow the user to draw outside the pixmap. # Project the point to the pixmap's edges. pos = self.intersectionPoint(self.current[-1], pos) elif len(self.current) > 1 and self.createMode == 'polygon' and\ self.closeEnough(pos, self.current[0]): # Attract line to starting point and # colorise to alert the user. pos = self.current[0] color = self.current.line_color self.overrideCursor(CURSOR_POINT) self.current.highlightVertex(0, Shape.NEAR_VERTEX) if self.createMode in ['polygon', 'linestrip']: self.line[0] = self.current[-1] self.line[1] = pos elif self.createMode == 'rectangle': self.line.points = [self.current[0], pos] self.line.close() elif self.createMode == 'circle': self.line.points = [self.current[0], pos] self.line.shape_type = "circle" elif self.createMode == 'line': self.line.points = [self.current[0], pos] self.line.close() elif self.createMode == 'point': self.line.points = [self.current[0]] self.line.close() self.line.line_color = color self.repaint() self.current.highlightClear() return # Polygon copy moving. if QtCore.Qt.RightButton & ev.buttons(): if self.selectedShapeCopy and self.prevPoint: self.overrideCursor(CURSOR_MOVE) self.boundedMoveShape(self.selectedShapeCopy, pos) self.repaint() elif self.selectedShape: self.selectedShapeCopy = self.selectedShape.copy() self.repaint() return # Polygon/Vertex moving. self.movingShape = False if QtCore.Qt.LeftButton & ev.buttons(): if self.selectedVertex(): self.boundedMoveVertex(pos) self.repaint() self.movingShape = True elif self.selectedShape and self.prevPoint: self.overrideCursor(CURSOR_MOVE) self.boundedMoveShape(self.selectedShape, pos) self.repaint() self.movingShape = True return # Just hovering over the canvas, 2 posibilities: # - Highlight shapes # - Highlight vertex # Update shape/vertex fill and tooltip value accordingly. self.setToolTip("Image") for shape in reversed([s for s in self.shapes if self.isVisible(s)]): # Look for a nearby vertex to highlight. If that fails, # check if we happen to be inside a shape. index = shape.nearestVertex(pos, self.epsilon / self.scale) index_edge = shape.nearestEdge(pos, self.epsilon / self.scale) if index is not None: if self.selectedVertex(): self.hShape.highlightClear() self.hVertex = index self.hShape = shape self.hEdge = index_edge shape.highlightVertex(index, shape.MOVE_VERTEX) self.overrideCursor(CURSOR_POINT) self.setToolTip("Click & drag to move point") self.setStatusTip(self.toolTip()) self.update() break elif shape.containsPoint(pos): if self.selectedVertex(): self.hShape.highlightClear() self.hVertex = None self.hShape = shape self.hEdge = index_edge self.setToolTip( "Click & drag to move shape '%s'" % shape.label) self.setStatusTip(self.toolTip()) self.overrideCursor(CURSOR_GRAB) self.update() break else: # Nothing found, clear highlights, reset state. if self.hShape: self.hShape.highlightClear() self.update() self.hVertex, self.hShape, self.hEdge = None, None, None self.edgeSelected.emit(self.hEdge is not None) def addPointToEdge(self): if (self.hShape is None and self.hEdge is None and self.prevMovePoint is None): return shape = self.hShape index = self.hEdge point = self.prevMovePoint shape.insertPoint(index, point) shape.highlightVertex(index, shape.MOVE_VERTEX) self.hShape = shape self.hVertex = index self.hEdge = None def mousePressEvent(self, ev): if QT5: pos = self.transformPos(ev.pos()) else: pos = self.transformPos(ev.posF()) if ev.button() == QtCore.Qt.LeftButton: if self.drawing(): if self.current: # Add point to existing shape. if self.createMode == 'polygon': self.current.addPoint(self.line[1]) self.line[0] = self.current[-1] if self.current.isClosed(): self.finalise() elif self.createMode in ['rectangle', 'circle', 'line']: assert len(self.current.points) == 1 self.current.points = self.line.points self.finalise() elif self.createMode == 'linestrip': self.current.addPoint(self.line[1]) self.line[0] = self.current[-1] if int(ev.modifiers()) == QtCore.Qt.ControlModifier: self.finalise() elif not self.outOfPixmap(pos): # Create new shape. self.current = Shape(shape_type=self.createMode) self.current.addPoint(pos) if self.createMode == 'point': self.finalise() else: if self.createMode == 'circle': self.current.shape_type = 'circle' self.line.points = [pos, pos] self.setHiding() self.drawingPolygon.emit(True) self.update() else: self.selectShapePoint(pos) self.prevPoint = pos self.repaint() elif ev.button() == QtCore.Qt.RightButton and self.editing(): self.selectShapePoint(pos) self.prevPoint = pos self.repaint() def mouseReleaseEvent(self, ev): if ev.button() == QtCore.Qt.RightButton: menu = self.menus[bool(self.selectedShapeCopy)] self.restoreCursor() if not menu.exec_(self.mapToGlobal(ev.pos()))\ and self.selectedShapeCopy: # Cancel the move by deleting the shadow copy. self.selectedShapeCopy = None self.repaint() elif ev.button() == QtCore.Qt.LeftButton and self.selectedShape: self.overrideCursor(CURSOR_GRAB) if self.movingShape: self.storeShapes() self.shapeMoved.emit() def endMove(self, copy=False): assert self.selectedShape and self.selectedShapeCopy shape = self.selectedShapeCopy # del shape.fill_color # del shape.line_color if copy: self.shapes.append(shape) self.selectedShape.selected = False self.selectedShape = shape self.repaint() else: shape.label = self.selectedShape.label self.deleteSelected() self.shapes.append(shape) self.storeShapes() self.selectedShapeCopy = None def hideBackroundShapes(self, value): self.hideBackround = value if self.selectedShape: # Only hide other shapes if there is a current selection. # Otherwise the user will not be able to select a shape. self.setHiding(True) self.repaint() def setHiding(self, enable=True): self._hideBackround = self.hideBackround if enable else False def canCloseShape(self): return self.drawing() and self.current and len(self.current) > 2 def mouseDoubleClickEvent(self, ev): # We need at least 4 points here, since the mousePress handler # adds an extra one before this handler is called. if self.canCloseShape() and len(self.current) > 3: self.current.popPoint() self.finalise() def selectShape(self, shape): self.deSelectShape() shape.selected = True self.selectedShape = shape self.setHiding() self.selectionChanged.emit(True) self.update() def selectShapePoint(self, point): """Select the first shape created which contains this point.""" self.deSelectShape() if self.selectedVertex(): # A vertex is marked for selection. index, shape = self.hVertex, self.hShape shape.highlightVertex(index, shape.MOVE_VERTEX) return for shape in reversed(self.shapes): if self.isVisible(shape) and shape.containsPoint(point): shape.selected = True self.selectedShape = shape self.calculateOffsets(shape, point) self.setHiding() self.selectionChanged.emit(True) return def calculateOffsets(self, shape, point): rect = shape.boundingRect() x1 = rect.x() - point.x() y1 = rect.y() - point.y() x2 = (rect.x() + rect.width() - 1) - point.x() y2 = (rect.y() + rect.height() - 1) - point.y() self.offsets = QtCore.QPoint(x1, y1), QtCore.QPoint(x2, y2) def boundedMoveVertex(self, pos): index, shape = self.hVertex, self.hShape point = shape[index] if self.outOfPixmap(pos): pos = self.intersectionPoint(point, pos) shape.moveVertexBy(index, pos - point) def boundedMoveShape(self, shape, pos): if self.outOfPixmap(pos): return False # No need to move o1 = pos + self.offsets[0] if self.outOfPixmap(o1): pos -= QtCore.QPoint(min(0, o1.x()), min(0, o1.y())) o2 = pos + self.offsets[1] if self.outOfPixmap(o2): pos += QtCore.QPoint(min(0, self.pixmap.width() - o2.x()), min(0, self.pixmap.height() - o2.y())) # XXX: The next line tracks the new position of the cursor # relative to the shape, but also results in making it # a bit "shaky" when nearing the border and allows it to # go outside of the shape's area for some reason. # self.calculateOffsets(self.selectedShape, pos) dp = pos - self.prevPoint if dp: shape.moveBy(dp) self.prevPoint = pos return True return False def deSelectShape(self): if self.selectedShape: self.selectedShape.selected = False self.selectedShape = None self.setHiding(False) self.selectionChanged.emit(False) self.update() def deleteSelected(self): if self.selectedShape: shape = self.selectedShape self.shapes.remove(self.selectedShape) self.storeShapes() self.selectedShape = None self.update() return shape def copySelectedShape(self): if self.selectedShape: shape = self.selectedShape.copy() self.deSelectShape() self.shapes.append(shape) self.storeShapes() shape.selected = True self.selectedShape = shape self.boundedShiftShape(shape) return shape def boundedShiftShape(self, shape): # Try to move in one direction, and if it fails in another. # Give up if both fail. point = shape[0] offset = QtCore.QPoint(2.0, 2.0) self.calculateOffsets(shape, point) self.prevPoint = point if not self.boundedMoveShape(shape, point - offset): self.boundedMoveShape(shape, point + offset) def paintEvent(self, event): if not self.pixmap: return super(Canvas, self).paintEvent(event) p = self._painter p.begin(self) p.setRenderHint(QtGui.QPainter.Antialiasing) p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing) p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) p.scale(self.scale, self.scale) p.translate(self.offsetToCenter()) p.drawPixmap(0, 0, self.pixmap) Shape.scale = self.scale for shape in self.shapes: if (shape.selected or not self._hideBackround) and \ self.isVisible(shape): shape.fill = shape.selected or shape == self.hShape shape.paint(p) if self.current: self.current.paint(p) self.line.paint(p) if self.selectedShapeCopy: self.selectedShapeCopy.paint(p) if (self.fillDrawing() and self.createMode == 'polygon' and self.current is not None and len(self.current.points) >= 2): drawing_shape = self.current.copy() drawing_shape.addPoint(self.line[1]) drawing_shape.fill = True drawing_shape.fill_color.setAlpha(64) drawing_shape.paint(p) p.end() def transformPos(self, point): """Convert from widget-logical coordinates to painter-logical ones.""" return point / self.scale - self.offsetToCenter() def offsetToCenter(self): s = self.scale area = super(Canvas, self).size() w, h = self.pixmap.width() * s, self.pixmap.height() * s aw, ah = area.width(), area.height() x = (aw - w) / (2 * s) if aw > w else 0 y = (ah - h) / (2 * s) if ah > h else 0 return QtCore.QPoint(x, y) def outOfPixmap(self, p): w, h = self.pixmap.width(), self.pixmap.height() return not (0 <= p.x() < w and 0 <= p.y() < h) def finalise(self): assert self.current self.current.close() self.shapes.append(self.current) self.storeShapes() self.current = None self.setHiding(False) self.newShape.emit() self.update() def closeEnough(self, p1, p2): # d = distance(p1 - p2) # m = (p1-p2).manhattanLength() # print "d %.2f, m %d, %.2f" % (d, m, d - m) # divide by scale to allow more precision when zoomed in return labelme.utils.distance(p1 - p2) < (self.epsilon / self.scale) def intersectionPoint(self, p1, p2): # Cycle through each image edge in clockwise fashion, # and find the one intersecting the current line segment. # http://paulbourke.net/geometry/lineline2d/ size = self.pixmap.size() points = [(0, 0), (size.width() - 1, 0), (size.width() - 1, size.height() - 1), (0, size.height() - 1)] x1, y1 = p1.x(), p1.y() x2, y2 = p2.x(), p2.y() d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points)) x3, y3 = points[i] x4, y4 = points[(i + 1) % 4] if (x, y) == (x1, y1): # Handle cases where previous point is on one of the edges. if x3 == x4: return QtCore.QPoint(x3, min(max(0, y2), max(y3, y4))) else: # y3 == y4 return QtCore.QPoint(min(max(0, x2), max(x3, x4)), y3) return QtCore.QPoint(x, y) def intersectingEdges(self, point1, point2, points): """Find intersecting edges. For each edge formed by `points', yield the intersection with the line segment `(x1,y1) - (x2,y2)`, if it exists. Also return the distance of `(x2,y2)' to the middle of the edge along with its index, so that the one closest can be chosen. """ (x1, y1) = point1 (x2, y2) = point2 for i in range(4): x3, y3 = points[i] x4, y4 = points[(i + 1) % 4] denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3) nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3) if denom == 0: # This covers two cases: # nua == nub == 0: Coincident # otherwise: Parallel continue ua, ub = nua / denom, nub / denom if 0 <= ua <= 1 and 0 <= ub <= 1: x = x1 + ua * (x2 - x1) y = y1 + ua * (y2 - y1) m = QtCore.QPoint((x3 + x4) / 2, (y3 + y4) / 2) d = labelme.utils.distance(m - QtCore.QPoint(x2, y2)) yield d, i, (x, y) # These two, along with a call to adjustSize are required for the # scroll area. def sizeHint(self): return self.minimumSizeHint() def minimumSizeHint(self): if self.pixmap: return self.scale * self.pixmap.size() return super(Canvas, self).minimumSizeHint() def wheelEvent(self, ev): if QT5: mods = ev.modifiers() delta = ev.angleDelta() if QtCore.Qt.ControlModifier == int(mods): # with Ctrl/Command key # zoom self.zoomRequest.emit(delta.y(), ev.pos()) else: # scroll self.scrollRequest.emit(delta.x(), QtCore.Qt.Horizontal) self.scrollRequest.emit(delta.y(), QtCore.Qt.Vertical) else: if ev.orientation() == QtCore.Qt.Vertical: mods = ev.modifiers() if QtCore.Qt.ControlModifier == int(mods): # with Ctrl/Command key self.zoomRequest.emit(ev.delta(), ev.pos()) else: self.scrollRequest.emit( ev.delta(), QtCore.Qt.Horizontal if (QtCore.Qt.ShiftModifier == int(mods)) else QtCore.Qt.Vertical) else: self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal) ev.accept() def keyPressEvent(self, ev): key = ev.key() if key == QtCore.Qt.Key_Escape and self.current: self.current = None self.drawingPolygon.emit(False) self.update() elif key == QtCore.Qt.Key_Return and self.canCloseShape(): self.finalise() def setLastLabel(self, text): assert text self.shapes[-1].label = text self.shapesBackups.pop() self.storeShapes() return self.shapes[-1] def undoLastLine(self): assert self.shapes self.current = self.shapes.pop() self.current.setOpen() if self.createMode in ['polygon', 'linestrip']: self.line.points = [self.current[-1], self.current[0]] elif self.createMode in ['rectangle', 'line', 'circle']: self.current.points = self.current.points[0:1] elif self.createMode == 'point': self.current = None self.drawingPolygon.emit(True) def undoLastPoint(self): if not self.current or self.current.isClosed(): return self.current.popPoint() if len(self.current) > 0: self.line[0] = self.current[-1] else: self.current = None self.drawingPolygon.emit(False) self.repaint() def loadPixmap(self, pixmap): self.pixmap = pixmap self.shapes = [] self.repaint() def loadShapes(self, shapes, replace=True): if replace: self.shapes = list(shapes) else: self.shapes.extend(shapes) self.storeShapes() self.current = None self.repaint() def setShapeVisible(self, shape, value): self.visible[shape] = value self.repaint() def overrideCursor(self, cursor): self.restoreCursor() self._cursor = cursor QtWidgets.QApplication.setOverrideCursor(cursor) def restoreCursor(self): QtWidgets.QApplication.restoreOverrideCursor() def resetState(self): self.restoreCursor() self.pixmap = None self.shapesBackups = [] self.update()
class MultiPlotWidget(QtWidgets.QWidget): closeSignal = QtCore.Signal() def __init__(self, context, parent=None): super(MultiPlotWidget, self).__init__() self._context = context layout = QtWidgets.QVBoxLayout() splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) self.quickEdit = QuickEditWidget(self) self.quickEdit.connect_x_range_changed(self._x_range_changed) self.quickEdit.connect_y_range_changed(self._y_range_changed) self.quickEdit.connect_errors_changed(self._errors_changed) self.quickEdit.connect_autoscale_changed(self._autoscale_changed) self.quickEdit.connect_plot_selection(self._selection_changed) # add some dummy plot self.plots = subplot(self._context) self.plots.connect_quick_edit_signal(self._update_quick_edit) self.plots.connect_rm_subplot_signal(self._update_quick_edit) # create GUI layout splitter.addWidget(self.plots) splitter.addWidget(self.quickEdit.widget) layout.addWidget(splitter) self.setLayout(layout) """ plotting """ def add_subplot(self, name): self.plots.add_subplot(name, len(self.quickEdit.get_subplots())) self.quickEdit.add_subplot(name) def plot(self, subplotName, ws, specNum=1): self.plots.plot(subplotName, ws, specNum=specNum) def remove_subplot(self, name): self.plots._remove_subplot(name) def get_subplots(self): return list(self._context.subplots.keys()) def add_vline_and_annotate(self, subplotName, xvalue, label, color): self.add_annotate(subplotName, label) self.add_vline(subplotName, xvalue, label.text, color) def rm_vline_and_annotate(self, subplotName, name): self.rm_annotate(subplotName, name) self.rm_vline(subplotName, name) def add_annotate(self, subplotName, label): self.plots.add_annotate(subplotName, label) def add_vline(self, subplotName, xvalue, name, color): self.plots.add_vline(subplotName, xvalue, name, color) def rm_annotate(self, subplotName, name): self.plots.rm_annotate(subplotName, name) def rm_vline(self, subplotName, name): self.plots.rm_vline(subplotName, name) # gets inital values for quickEdit def set_all_values(self): names = self.quickEdit.get_selection() xrange = list(self._context.subplots[names[0]].xbounds) yrange = list(self._context.subplots[names[0]].ybounds) for name in names: xbounds = self._context.subplots[name].xbounds ybounds = self._context.subplots[name].ybounds if xrange[0] > xbounds[0]: xrange[0] = deepcopy(xbounds[0]) if xrange[1] < xbounds[1]: xrange[1] = deepcopy(xbounds[1]) if yrange[0] > ybounds[0]: yrange[0] = deepcopy(ybounds[0]) if yrange[1] < ybounds[1]: yrange[1] = deepcopy(ybounds[1]) self._context.set_xBounds(xrange) self._context.set_yBounds(yrange) self._x_range_changed(xrange) self._y_range_changed(yrange) # get tick boxes correct errors = self._check_all_errors(names) self.quickEdit.set_errors(errors) self._change_errors(errors, names) def connectCloseSignal(self, slot): self.closeSignal.connect(slot) def removeSubplotConnection(self, slot): self.plots.connect_rm_subplot_signal(slot) def disconnectCloseSignal(selft): self.closeSignal.disconnect() def removeSubplotDisonnect(self): self.plots.disconnect_rm_subplot_signal() """ update GUI """ def _if_empty_close(self): if not self._context.subplots: self.closeSignal.emit() self.close def _update_quick_edit(self, subplotName): names = self.quickEdit.get_selection() if subplotName not in self._context.subplots.keys(): self.quickEdit.rm_subplot(subplotName) self._if_empty_close() return xrange = self._context.subplots[subplotName].xbounds yrange = self._context.subplots[subplotName].ybounds if len(names) == 0: return # if all selected update everyone if len(names) > 1: self.quickEdit.set_plot_x_range(xrange) self.quickEdit.set_plot_y_range(yrange) # if changed current selection elif names[0] == subplotName: self.quickEdit.set_plot_x_range(xrange) self.quickEdit.set_plot_y_range(yrange) # if a different plot changed else: pass def _selection_changed(self, index): names = self.quickEdit.get_selection() xrange = self._context.get_xBounds() yrange = self._context.get_yBounds() errors = True if len(names) == 1: xrange = self._context.subplots[names[0]].xbounds yrange = self._context.subplots[names[0]].ybounds errors = self._context.subplots[names[0]].errors else: errors = self._check_all_errors(names) # update values self.quickEdit.set_errors(errors) self._change_errors(errors, names) self.quickEdit.set_plot_x_range(xrange) self.quickEdit.set_plot_y_range(yrange) # force update of plots if selection is all if len(names) > 1: self._x_range_changed(xrange) self._y_range_changed(yrange) def _autoscale_changed(self, state): names = self.quickEdit.get_selection() self.plots.set_y_autoscale(names, True) def _change_errors(self, state, names): self.plots.change_errors(state, names) def _errors_changed(self, state): names = self.quickEdit.get_selection() self._change_errors(state, names) def _x_range_changed(self, xRange): names = self.quickEdit.get_selection() if len(names) > 1: self._context.set_xBounds(xRange) self.plots.set_plot_x_range(names, xRange) self.quickEdit.set_plot_x_range(xRange) def _y_range_changed(self, yRange): names = self.quickEdit.get_selection() if len(names) > 1: self._context.set_yBounds(yRange) self.plots.set_plot_y_range(names, yRange) self.quickEdit.set_plot_y_range(yRange) def _check_all_errors(self, names): for name in names: if self._context.subplots[name].errors is False: return False return True
class CounterLogic(GenericLogic): """ This logic module gathers data from a hardware counting device. @signal sigCounterUpdate: there is new counting data available @signal sigCountContinuousNext: used to simulate a loop in which the data acquisition runs. @sigmal sigCountGatedNext: ??? @return error: 0 is OK, -1 is error """ # Signals and slots are used for communication between objects. A signal is emitted when a particular event occurs. # A slot is a function that is called in response to a particular signal. # The documentation of the Qt's signals and slots can be found on this link: # https://doc.qt.io/qt-5/signalsandslots.html sigCounterUpdated = QtCore.Signal() sigCountDataNext = QtCore.Signal() sigGatedCounterFinished = QtCore.Signal() sigGatedCounterContinue = QtCore.Signal(bool) sigCountingSamplesChanged = QtCore.Signal(int) sigCountLengthChanged = QtCore.Signal(int) sigCountFrequencyChanged = QtCore.Signal(float) sigSavingStatusChanged = QtCore.Signal(bool) sigCountStatusChanged = QtCore.Signal(bool) sigCountingModeChanged = QtCore.Signal(CountingMode) _modclass = 'CounterLogic' _modtype = 'logic' ## declare connectors (slow counter hardware corresponding to the counter_logic) # Connector() is imported from core module counter1 = Connector(interface='SlowCounterInterface') savelogic = Connector(interface='SaveLogic') # status vars of the counter _count_length = StatusVar( 'count_length', 300) # counter's shown length is 300 measurements _smooth_window_length = StatusVar( 'smooth_window_length', 10) # 10 data points on moving average for smoothing data _counting_samples = StatusVar( 'counting_samples', 1) # oversampling parameter (measurements per data point) _count_frequency = StatusVar('count_frequency', 50) # number of measurements per second _saving = StatusVar('saving', False) # instantiation for counter_logic def __init__(self, config, **kwargs): """ Create CounterLogic object with connectors. @param dict config: module configuration @param dict kwargs: optional parameters """ super().__init__(config=config, **kwargs) # locking for thread safety # Mutex is a mutual exclusion object that synchronizes access to a resource. # Mutex is a locking mechanism that makes sure only one thread can acquire the Mutex at a time and enter the # critical section. This thread only releases the Mutex when it exits the critical section. """ example of using Mutex(): wait(mutex); ... (critical section) ... signal(mutex); """ self.threadlock = Mutex() self.log.debug('The following configuration was found.') # checking for the right configuration for key in config.keys(): self.log.debug('{0}: {1}'.format(key, config[key])) # in bins (vars explained in comments around line 70) self._count_length = 300 self._smooth_window_length = 10 self._counting_samples = 1 # in hertz self._count_frequency = 50 # self._binned_counting = True # UNUSED? # This is the default counting mode - continuous self._counting_mode = CountingMode['CONTINUOUS'] self._saving = False return def on_activate(self): """ Initialisation performed during activation of the module. """ # Connect to hardware and save logic (vars defined in line 67, 68) # (explained in SlowCounterInterface and SaveLogic) self._counting_device = self.counter1() self._save_logic = self.savelogic() # Recall saved app-parameters if 'counting_mode' in self._statusVariables: self._counting_mode = CountingMode[ self._statusVariables['counting_mode']] # function defined around line 169 constraints = self.get_hardware_constraints() number_of_detectors = constraints.max_detectors # initialize data arrays (vars explained in comments around line 70) # get_channels() defined in bottom of this file self.countdata = np.zeros( [len(self.get_channels()), self._count_length]) self.countdata_smoothed = np.zeros( [len(self.get_channels()), self._count_length]) self.rawdata = np.zeros( [len(self.get_channels()), self._counting_samples]) self._already_counted_samples = 0 # The variable is an index for gated counting self._data_to_save = [] # Flag to stop the loop self.stopRequested = False # time: Return the current time in seconds since the Epoch. self._saving_start_time = time.time() # connect signals to the counter self.sigCountDataNext.connect(self.count_loop_body, QtCore.Qt.QueuedConnection) return def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ # Save parameters to disk self._statusVariables['counting_mode'] = self._counting_mode.name # Stop measurement # lock is used as a synchronization tool, preventing threads outputting at the same time if self.module_state() == 'locked': self._stopCount_wait( ) # function defined in the bottom of this file # disconnect the signal from counter self.sigCountDataNext.disconnect() return def get_hardware_constraints(self): """ Retrieve the hardware constraints from the counter device. @return SlowCounterConstraints: object with constraints for the counter """ # the get_constraints() function is defined in hardware/national_instruments_x_series.py return self._counting_device.get_constraints() def set_counting_samples(self, samples=1 ): # function of setting the oversampling number """ Sets the length of the counted bins. The counter is stopped first and restarted afterwards. @param int samples: oversampling in units of bins (positive int ). @return int: oversampling in units of bins. """ # Determine if the counter has to be restarted after setting the parameter # param restart: whether the counter restart or not # a locked state means the counter is running if self.module_state() == 'locked': restart = True else: restart = False if samples > 0: self._stopCount_wait() self._counting_samples = int(samples) # if the counter was running, restart it if restart: self.startCount( ) # startCount() redirects to counting mode's start function else: self.log.warning( 'counting_samples has to be larger than 0! Command ignored!') self.sigCountingSamplesChanged.emit(self._counting_samples) return self._counting_samples def set_count_length(self, length=300): """ Sets the time trace in units of bins. @param int length: time trace in units of bins (positive int). @return int: length of time trace in units of bins This makes sure, the counter is stopped first and restarted afterwards. """ if self.module_state() == 'locked': restart = True else: restart = False if length > 0: self._stopCount_wait() # explained in the bottom of the file self._count_length = int(length) # explained in line 70 # if the counter was running, restart it if restart: self.startCount() else: self.log.warning( 'count_length has to be larger than 0! Command ignored!') self.sigCountLengthChanged.emit(self._count_length) return self._count_length def set_count_frequency(self, frequency=50): """ Sets the frequency with which the data is acquired. @param float frequency: the desired frequency of counting in Hz @return float: the actual frequency of counting in Hz This makes sure, the counter is stopped first and restarted afterwards. """ constraints = self.get_hardware_constraints( ) # function defined around line 169 # restart the measurement if it's already running if self.module_state() == 'locked': restart = True else: restart = False if constraints.min_count_frequency <= frequency <= constraints.max_count_frequency: # stop the counter and set to this frequency in range self._stopCount_wait() self._count_frequency = frequency # if the counter was running, restart it if restart: self.startCount() # if the default frequency (50 Hz) is not in range, display error else: self.log.warning('count_frequency not in range! Command ignored!') self.sigCountFrequencyChanged.emit(self._count_frequency) return self._count_frequency def get_count_length(self): """ Returns the currently set length of the counting array. @return int: count_length """ return self._count_length # defined around line 70 # FIXME: get the frequency from the slow counter hardware def get_count_frequency(self): """ Returns the currently set frequency of counting (resolution). @return float: count_frequency """ return self._count_frequency def get_counting_samples(self): """ Returns the currently set number of samples counted per readout. @return int: counting_samples """ return self._counting_samples def get_saving_state(self): """ Returns if the data is saved in the moment. @return bool: saving state """ return self._saving def start_saving(self, resume=False): """ Sets up start-time and initializes data array, if not resuming, and changes saving state. If the counter is not running it will be started in order to have data to save. @return bool: saving state """ if not resume: self._data_to_save = [] self._saving_start_time = time.time( ) # time: Return the current time in seconds since the Epoch. self._saving = True # If the counter is not running, then it should start running so there is data to save if self.module_state() != 'locked': self.startCount() # the "emit()" sends signal to the slot function, executing the slot codes self.sigSavingStatusChanged.emit(self._saving) return self._saving # save data to file def save_data(self, to_file=True, postfix='', save_figure=True): """ Save the counter trace data and writes it to a file. @param bool to_file: indicate, whether data have to be saved to file @param str postfix: an additional tag, which will be added to the filename upon save @param bool save_figure: select whether png and pdf should be saved @return dict parameters: Dictionary which contains the saving parameters """ # stop saving thus saving state has to be set to False self._saving = False self._saving_stop_time = time.time() # write the parameters: # strftime(format[, tuple]) -> string. # Convert a time tuple to a string according to a format specification. # localtime([seconds]) -> (tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst) # Convert seconds since the Epoch to a time tuple expressing local time parameters = OrderedDict() parameters['Start counting time'] = time.strftime( '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_start_time)) parameters['Stop counting time'] = time.strftime( '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time)) parameters['Count frequency (Hz)'] = self._count_frequency parameters['Oversampling (Samples)'] = self._counting_samples parameters[ 'Smooth Window Length (# of events)'] = self._smooth_window_length if to_file: # If there is a postfix then add separating underscore if postfix == '': filelabel = 'count_trace' else: filelabel = 'count_trace_' + postfix # prepare the data in a dict or in an OrderedDict: header = 'Time (s)' for i, detector in enumerate(self.get_channels()): header = header + ',Signal{0} (counts/s)'.format(i) data = {header: self._data_to_save} filepath = self._save_logic.get_path_for_module( module_name='Counter') if save_figure: fig = self.draw_figure(data=np.array(self._data_to_save)) else: fig = None self._save_logic.save_data(data, filepath=filepath, parameters=parameters, filelabel=filelabel, plotfig=fig, delimiter='\t') self.log.info('Counter Trace saved to:\n{0}'.format(filepath)) self.sigSavingStatusChanged.emit(self._saving) return self._data_to_save, parameters def draw_figure(self, data): # data in array format """ Draw figure to save with data file. @param: nparray data: a numpy array containing counts vs time for all detectors @return: fig fig: a matplotlib figure object to be saved to file. """ count_data = data[:, 1:len(self.get_channels( )) + 1] # total counter channels number (all elements in 2nd column) time_data = data[:, 0] # all elements in 1st column should be the time data # Scale count values using SI prefix (converting units in '', 'k', 'M', 'G') prefix = ['', 'k', 'M', 'G'] prefix_index = 0 while np.max(count_data) > 1000: count_data = count_data / 1000 prefix_index = prefix_index + 1 counts_prefix = prefix[prefix_index] # Use qudi style plt.style.use(self._save_logic.mpl_qd_style) # Create figure fig, ax = plt.subplots() ax.plot(time_data, count_data, linestyle=':', linewidth=0.5) ax.set_xlabel('Time (s)') ax.set_ylabel('Fluorescence (' + counts_prefix + 'c/s)') return fig def set_counting_mode(self, mode='CONTINUOUS'): """Set the counting mode, to change between continuous and gated counting. Possible options are: 'CONTINUOUS' = counts continuously 'GATED' = bins the counts according to a gate signal 'FINITE_GATED' = finite measurement with predefined number of samples @return str: counting mode """ constraints = self.get_hardware_constraints() if self.module_state() != 'locked': if CountingMode[mode] in constraints.counting_mode: self._counting_mode = CountingMode[mode] self.log.debug('New counting mode: {}'.format( self._counting_mode)) else: self.log.warning( 'Counting mode not supported from hardware. Command ignored!' ) self.sigCountingModeChanged.emit(self._counting_mode) else: self.log.error( 'Cannot change counting mode while counter is still running.') return self._counting_mode def get_counting_mode(self): """ Retrieve the current counting mode. @return str: one of the possible counting options: 'CONTINUOUS' = counts continuously 'GATED' = bins the counts according to a gate signal 'FINITE_GATED' = finite measurement with predefined number of samples """ return self._counting_mode # FIXME: Not implemented for self._counting_mode == 'gated' def startCount(self): """ This is called externally, and is basically a wrapper that redirects to the chosen counting mode start function. @return error: 0 is OK, -1 is error """ # Sanity checks constraints = self.get_hardware_constraints( ) # defined around line 169 if self._counting_mode not in constraints.counting_mode: self.log.error( 'Unknown counting mode "{0}". Cannot start the counter.' ''.format(self._counting_mode)) self.sigCountStatusChanged.emit(False) return -1 with self.threadlock: # Lock module if self.module_state() != 'locked': self.module_state.lock() else: self.log.warning( 'Counter already running. Method call ignored.') return 0 # Set up clock (the function set_up_clock() is defined in hardware/national_instruments_x_series.py) clock_status = self._counting_device.set_up_clock( clock_frequency=self._count_frequency) if clock_status < 0: self.module_state.unlock() self.sigCountStatusChanged.emit(False) return -1 # Set up counter (the function set_up_counter() is defined in hardware/national_instruments_x_series.py) if self._counting_mode == CountingMode['FINITE_GATED']: counter_status = self._counting_device.set_up_counter( counter_buffer=self._count_length) # elif self._counting_mode == CountingMode['GATED']: # else: counter_status = self._counting_device.set_up_counter() if counter_status < 0: self._counting_device.close_clock() self.module_state.unlock() self.sigCountStatusChanged.emit(False) return -1 # initialising the data arrays # rawdata: create a num of channels x oversampling, 2 dimensional array with 0's self.rawdata = np.zeros( [len(self.get_channels()), self._counting_samples]) # countdata: create a num of channels x count length, 2 dimensional array with 0's self.countdata = np.zeros( [len(self.get_channels()), self._count_length]) # countdata_smoothed: # create a num of channels x count length, 2 dimensional array with 0's (same size with above) self.countdata_smoothed = np.zeros( [len(self.get_channels()), self._count_length]) # _sampling_data: create a num of channels x oversampling num, 2 dimensional array without initializing data # empty (Return a new array of given shape and type, without initializing entries.) self._sampling_data = np.empty( [len(self.get_channels()), self._counting_samples]) # the sample index for gated counting self._already_counted_samples = 0 # Start data reader loop self.sigCountStatusChanged.emit(True) self.sigCountDataNext.emit() return def stopCount(self): """ Set a flag to request stopping counting. """ if self.module_state() == 'locked': with self.threadlock: self.stopRequested = True return def count_loop_body(self): """ This method gets the count data from the hardware for the continuous counting mode (default). It runs repeatedly in the logic module event loop by being connected to sigCountContinuousNext and emitting sigCountContinuousNext through a queued connection. """ if self.module_state() == 'locked': with self.threadlock: # check for aborts of the thread in break if necessary if self.stopRequested: # close off the actual counter cnt_err = self._counting_device.close_counter() clk_err = self._counting_device.close_clock() if cnt_err < 0 or clk_err < 0: self.log.error( 'Could not even close the hardware, giving up.') # switch the state variable off again self.stopRequested = False self.module_state.unlock() self.sigCounterUpdated.emit() return # read the current counter value (vars explained around line 466) # the function get_counter() defined in hardware/national_instruments_x_series.py self.rawdata = self._counting_device.get_counter( samples=self._counting_samples) if self.rawdata[0, 0] < 0: self.log.error( 'The counting went wrong, killing the counter.') self.stopRequested = True else: if self._counting_mode == CountingMode['CONTINUOUS']: self._process_data_continous( ) # defined in the bottom of the file elif self._counting_mode == CountingMode['GATED']: self._process_data_gated( ) # defined in the bottom of the file elif self._counting_mode == CountingMode['FINITE_GATED']: self._process_data_finite_gated( ) # defined in the bottom of the file else: self.log.error( 'No valid counting mode set! Can not process counter data.' ) # call this again from event loop self.sigCounterUpdated.emit() self.sigCountDataNext.emit() return def save_current_count_trace(self, name_tag=''): """ The currently displayed counttrace will be saved. @param str name_tag: optional, personal description that will be appended to the file name @return: dict data: Data which was saved str filepath: Filepath dict parameters: Experiment parameters str filelabel: Filelabel This method saves the already displayed counts to file and does not accumulate them. The counttrace variable will be saved to file with the provided name! """ # If there is a postfix then add separating underscore if name_tag == '': filelabel = 'snapshot_count_trace' else: filelabel = 'snapshot_count_trace_' + name_tag stop_time = self._count_length / self._count_frequency time_step_size = stop_time / len(self.countdata) x_axis = np.arange(0, stop_time, time_step_size) # prepare the data in a dict or in an OrderedDict: data = OrderedDict() chans = self.get_channels() savearr = np.empty((len(chans) + 1, len(x_axis))) savearr[0] = x_axis datastr = 'Time (s)' for i, ch in enumerate(chans): savearr[i + 1] = self.countdata[i] datastr += ',Signal {0} (counts/s)'.format(i) data[datastr] = savearr.transpose() # write the parameters: parameters = OrderedDict() timestr = time.strftime('%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(time.time())) parameters['Saved at time'] = timestr parameters['Count frequency (Hz)'] = self._count_frequency parameters['Oversampling (Samples)'] = self._counting_samples parameters[ 'Smooth Window Length (# of events)'] = self._smooth_window_length filepath = self._save_logic.get_path_for_module(module_name='Counter') self._save_logic.save_data(data, filepath=filepath, parameters=parameters, filelabel=filelabel, delimiter='\t') self.log.debug('Current Counter Trace saved to: {0}'.format(filepath)) return data, filepath, parameters, filelabel def get_channels(self): """ Shortcut for hardware get_counter_channels. @return list(str): return list of active counter channel names """ # (function get_counter_channels() defined in hardware/national_instruments_x_series.py) return self._counting_device.get_counter_channels() def _process_data_continous(self): """ Processes the raw data from the counting device @return: """ for i, ch in enumerate(self.get_channels()): # remember the new count data in circular array self.countdata[i, 0] = np.average(self.rawdata[i]) # move the array to the left to make space for the new data self.countdata = np.roll(self.countdata, -1, axis=1) # also move the smoothing array self.countdata_smoothed = np.roll(self.countdata_smoothed, -1, axis=1) # calculate the median and save it window = -int(self._smooth_window_length / 2) - 1 for i, ch in enumerate(self.get_channels()): self.countdata_smoothed[i, window:] = np.median( self.countdata[i, -self._smooth_window_length:]) # save the data if necessary if self._saving: # if oversampling is necessary if self._counting_samples > 1: chans = self.get_channels() self._sampling_data = np.empty( [len(chans) + 1, self._counting_samples]) self._sampling_data[ 0, :] = time.time() - self._saving_start_time for i, ch in enumerate(chans): self._sampling_data[i + 1, 0] = self.rawdata[i] self._data_to_save.extend(list(self._sampling_data)) # if we don't want to use oversampling else: # append tuple to data stream (timestamp, average counts) chans = self.get_channels() newdata = np.empty((len(chans) + 1, )) newdata[0] = time.time() - self._saving_start_time for i, ch in enumerate(chans): newdata[i + 1] = self.countdata[i, -1] self._data_to_save.append(newdata) return def _process_data_gated(self): """ Processes the raw data from the counting device @return: """ # remember the new count data in circular array self.countdata[0] = np.average(self.rawdata[0]) # move the array to the left to make space for the new data self.countdata = np.roll(self.countdata, -1) # also move the smoothing array self.countdata_smoothed = np.roll(self.countdata_smoothed, -1) # calculate the median and save it self.countdata_smoothed[-int(self._smooth_window_length / 2) - 1:] = np.median( self. countdata[-self._smooth_window_length:]) # save the data if necessary if self._saving: # if oversampling is necessary if self._counting_samples > 1: self._sampling_data = np.empty((self._counting_samples, 2)) # first column is time data, second column is value self._sampling_data[:, 0] = time.time() - self._saving_start_time self._sampling_data[:, 1] = self.rawdata[0] self._data_to_save.extend(list(self._sampling_data)) # if we don't want to use oversampling else: # append tuple to data stream (timestamp, average counts) self._data_to_save.append( np.array((time.time() - self._saving_start_time, self.countdata[-1]))) return def _process_data_finite_gated(self): """ Processes the raw data from the counting device @return: """ if self._already_counted_samples + len(self.rawdata[0]) >= len( self.countdata): needed_counts = len(self.countdata) - self._already_counted_samples self.countdata[0:needed_counts] = self.rawdata[0][0:needed_counts] self.countdata = np.roll(self.countdata, -needed_counts) self._already_counted_samples = 0 self.stopRequested = True else: # replace the first part of the array with the new data: self.countdata[0:len(self.rawdata[0])] = self.rawdata[0] # roll the array by the amount of data it had been inserted: self.countdata = np.roll(self.countdata, -len(self.rawdata[0])) # increment the index counter: self._already_counted_samples += len(self.rawdata[0]) return def _stopCount_wait(self, timeout=5.0): """ Stops the counter and waits until it actually has stopped. @param timeout: float, the max. time in seconds how long the method should wait for the process to stop. @return: error code """ self.stopCount() start_time = time.time() while self.module_state() == 'locked': time.sleep(0.1) # if the counter is still running after the timeout duration then error returns if time.time() - start_time >= timeout: self.log.error( 'Stopping the counter timed out after {0}s'.format( timeout)) return -1 return 0
def __init__(self): super().__init__() # Get FontAwesome 5.x icons by name in various styles by name fa5_icon = qta.icon('fa5.flag') fa5_button = QtWidgets.QPushButton(fa5_icon, 'Font Awesome! (regular)') fa5s_icon = qta.icon('fa5s.flag') fa5s_button = QtWidgets.QPushButton(fa5s_icon, 'Font Awesome! (solid)') fa5b_icon = qta.icon('fa5b.github') fa5b_button = QtWidgets.QPushButton(fa5b_icon, 'Font Awesome! (brands)') # Get Elusive icons by name asl_icon = qta.icon('ei.asl') elusive_button = QtWidgets.QPushButton(asl_icon, 'Elusive Icons!') # Get Material Design icons by name apn_icon = qta.icon('mdi.access-point-network') mdi_button = QtWidgets.QPushButton(apn_icon, 'Material Design Icons!') # Rotated rot_icon = qta.icon('mdi.access-point-network', rotated=45) rot_button = QtWidgets.QPushButton(rot_icon, 'Rotated Icons!') # Horizontal flip hflip_icon = qta.icon('mdi.account-alert', hflip=True) hflip_button = QtWidgets.QPushButton(hflip_icon, 'Horizontally Flipped Icons!') # Vertical flip vflip_icon = qta.icon('mdi.account-alert', vflip=True) vflip_button = QtWidgets.QPushButton(vflip_icon, 'Vertically Flipped Icons!') # Styling styling_icon = qta.icon('fa5s.music', active='fa5s.balance-scale', color='blue', color_active='orange') music_button = QtWidgets.QPushButton(styling_icon, 'Styling') # Toggle toggle_icon = qta.icon('fa5s.home', selected='fa5s.balance-scale', color_off='black', color_off_active='blue', color_on='orange', color_on_active='yellow') toggle_button = QtWidgets.QPushButton(toggle_icon, 'Toggle') toggle_button.setCheckable(True) iconwidget = qta.IconWidget() spin_icon = qta.icon('mdi.loading', color='red', animation=qta.Spin(iconwidget)) iconwidget.setIcon(spin_icon) iconwidget.setIconSize(QtCore.QSize(32, 32)) iconwidgetholder = QtWidgets.QWidget() lo = QtWidgets.QHBoxLayout() lo.addWidget(iconwidget) lo.addWidget(QtWidgets.QLabel('IconWidget')) iconwidgetholder.setLayout(lo) iconwidget2 = qta.IconWidget('mdi.web', color='blue') # Stack icons camera_ban = qta.icon('fa5s.camera', 'fa5s.ban', options=[{ 'scale_factor': 0.5, 'active': 'fa5s.balance-scale' }, { 'color': 'red', 'opacity': 0.7 }]) stack_button = QtWidgets.QPushButton(camera_ban, 'Stack') stack_button.setIconSize(QtCore.QSize(32, 32)) # Stack and offset icons saveall = qta.icon('fa5.save', 'fa5.save', options=[{ 'scale_factor': 0.8, 'offset': (0.2, 0.2), 'color': 'gray' }, { 'scale_factor': 0.8 }]) saveall_button = QtWidgets.QPushButton(saveall, 'Stack, offset') # Spin icons spin_button = QtWidgets.QPushButton(' Spinning icon') spin_icon = qta.icon('fa5s.spinner', color='red', animation=qta.Spin(spin_button)) spin_button.setIcon(spin_icon) # Pulse icons pulse_button = QtWidgets.QPushButton(' Pulsing icon') pulse_icon = qta.icon('fa5s.spinner', color='green', animation=qta.Pulse(pulse_button)) pulse_button.setIcon(pulse_icon) # Stacked spin icons stack_spin_button = QtWidgets.QPushButton('Stack spin') options = [{ 'scale_factor': 0.4, 'animation': qta.Spin(stack_spin_button) }, { 'color': 'blue' }] stack_spin_icon = qta.icon('ei.asl', 'fa5.square', options=options) stack_spin_button.setIcon(stack_spin_icon) stack_spin_button.setIconSize(QtCore.QSize(32, 32)) # Render a label with this font label = QtWidgets.QLabel(chr(0xf19c) + ' ' + 'Label') label.setFont(qta.font('fa', 16)) # Layout vbox = QtWidgets.QVBoxLayout() widgets = [ fa5_button, fa5s_button, fa5b_button, elusive_button, mdi_button, music_button, rot_button, hflip_button, vflip_button, toggle_button, stack_button, saveall_button, spin_button, pulse_button, stack_spin_button, label, iconwidgetholder, iconwidget2 ] for w in widgets: vbox.addWidget(w) self.setLayout(vbox) self.setWindowTitle('Awesome') self.show()
class BaseSettings(QtCore.QObject): _individual_rendering_parameters = True # signals defined here because qt has a problem with multiple inheritance and I can't get it running if # they are defined in the matching classes... individualColormapChanged = QtCore.Signal() individualClippingPlaneChanged = QtCore.Signal() individualLightChanged = QtCore.Signal() def __init__(self): super().__init__() self._createParameters() if _have_qt: self._createQtWidget() def getSettings(self): res = {} for group in self._parameters: for p in self._parameters[group]: res[p.name] = p.getValue() return res def setSettings(self, settings): for group in self._parameters: for p in self._parameters[group]: if p.name in settings: p.setValue(settings[p.name]) def __getstate__(self): return (self._parameters, ) def __setstate__(self, state): self._parameters = {} self._par_name_dict = {} for group, items in state[0].items(): self.addParameters(group, *items) if _have_qt: self._createQtWidget() def _createParameters(self): self._parameters = {} self._par_name_dict = {} @inmain_decorator(True) def _createQtWidget(self): self.widgets = wid.OptionWidgets() for group, params in self._parameters.items(): for param in params: if param.getOption("updateWidgets"): param.changed.connect( lambda *a, **b: self.widgets.update()) widgets = [par.getWidget() for par in params] self.widgets.addGroup(group, *widgets) @inmain_decorator(True) def updateWidgets(self): """Updates setting widgets""" self.widgets.update() def _attachParameter(self, parameter): if parameter.name: if hasattr(parameter, "getValue"): setattr(self, "get" + parameter.name, parameter.getValue) setattr(self, "set" + parameter.name, parameter.setValue) parameter._attachTo(self) def _connectParameters(self): pass def addParameters(self, group, *parameters): if group not in self._parameters: self._parameters[group] = [] for par in parameters: self._parameters[group].append(par) self._attachParameter(par)
class LayerCommunicator(QtCore.QObject): layer_check_changed = QtCore.Signal(object, bool)
class ScientificDoubleSpinBox(QtWidgets.QDoubleSpinBox): resetValueChanged = QtCore.Signal(float) def __init__(self, reset_popup=True, *args, **kwargs): self.validator = FloatValidator() if 'numFormat' in kwargs: self.numFormat = kwargs.pop('numFormat') else: self.numFormat = 'g' self.setStrDecimals(6) # number of decimals displayed super().__init__(*args, **kwargs) self.cb = QApplication.clipboard() self.setKeyboardTracking(False) self.setMinimum(-sys.float_info.max) self.setMaximum(sys.float_info.max) self.setDecimals(int(np.floor(np.log10(sys.float_info.max)))) # big for setting value self.setSingleStep(0.1) self.setAccelerated(True) # self.installEventFilter(self) if 'value' in kwargs: self.setValue(kwargs['value']) else: self.setValue(0) self._set_reset_value(self.value()) if reset_popup: # Set popup self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self._popup_menu) # Set Shortcuts shortcut_fcn_pair = [['Ctrl+R', lambda: self._reset()], ['Ctrl+C', lambda: self._copy()], ['Ctrl+V', lambda: self._paste()]] for shortcut, fcn in shortcut_fcn_pair: # TODO: need to fix hover shortcuts not working QShortcut(QtGui.QKeySequence(shortcut), self, activated=fcn, context=QtCore.Qt.WidgetShortcut) # def eventFilter(self, obj, event): # event filter to allow hover shortcuts # if event.type() == QtCore.QEvent.Enter: # self.setFocus() # return True # elif event.type() == QtCore.QEvent.Leave: # return False # else: # return super().eventFilter(obj, event) def _popup_menu(self, event): popup_menu = QMenu(self) popup_menu.addAction('Reset', lambda: self._reset(), 'Ctrl+R') popup_menu.addSeparator() popup_menu.addAction('Copy', lambda: self._copy(), 'Ctrl+C') popup_menu.addAction('Paste', lambda: self._paste(), 'Ctrl+V') popup_menu.addSeparator() popup_menu.addAction('Set Reset Value', lambda: self._set_reset_value(self.value())) popup_menu.exec_(QtGui.QCursor.pos()) def _reset(self, silent=False): self.blockSignals(True) # needed because shortcut isn't always signalling valueChanged.emit self.setValue(self.reset_value) self.blockSignals(False) if not silent: self.valueChanged.emit(self.reset_value) def setStrDecimals(self, value: int): self.strDecimals = value def _set_reset_value(self, value): self.reset_value = value self.resetValueChanged.emit(self.reset_value) def _copy(self): self.selectAll() cb = self.cb cb.clear(mode=cb.Clipboard) cb.setText(self.textFromValue(self.value()), mode=cb.Clipboard) def _paste(self): previous_value = self.text() if self.fixup(self.cb.text()): self.setValue(float(self.fixup(self.cb.text()))) else: self.setValue(float(previous_value)) def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Paste): self._paste() super(ScientificDoubleSpinBox, self).keyPressEvent(event) # don't want to overwrite all shortcuts def validate(self, text, position): return self.validator.validate(text, position) def fixup(self, text): return self.validator.fixup(text) def valueFromText(self, text): return float(text) def textFromValue(self, value): """Modified form of the 'g' format specifier.""" if 'g' in self.numFormat: string = "{:.{dec}{numFormat}}".format(value, dec=self.strDecimals, numFormat=self.numFormat) elif 'e' in self.numFormat: string = "{:.{dec}{numFormat}}".format(value, dec=self.strDecimals, numFormat=self.numFormat) string = re.sub("e(-?)0*(\d+)", r"e\1\2", string.replace("e+", "e")) return string def stepBy(self, steps): if self.specialValueText() and self.value() == self.minimum(): text = self.textFromValue(self.minimum()) else: text = self.cleanText() old_val = float(text) if self.numFormat == 'g' and OoM(old_val) <= self.strDecimals: # my own custom g val = old_val + self.singleStep()*steps new_string = "{:.{dec}f}".format(val, dec=self.strDecimals) else: old_OoM = OoM(old_val) val = old_val + np.power(10, old_OoM)*self.singleStep()*steps new_OoM = OoM(val) if old_OoM > new_OoM: # needed to step down by new amount 1E5 -> 9.9E6 val = old_val + np.power(10, new_OoM)*self.singleStep()*steps new_string = "{:.{dec}e}".format(val, dec=self.strDecimals) self.lineEdit().setText(new_string) self.setValue(float(new_string))
class WorkbenchNavigationToolbar(NavigationToolbar2QT): sig_grid_toggle_triggered = QtCore.Signal() sig_active_triggered = QtCore.Signal() sig_hold_triggered = QtCore.Signal() toolitems = (('Home', 'Reset original view', 'fa.home', 'home', None), ('Pan', 'Pan axes with left mouse, zoom with right', 'fa.arrows-alt', 'pan', False), ('Zoom', 'Zoom to rectangle', 'fa.search-plus', 'zoom', False), (None, None, None, None, None), ('Grid', 'Toggle grid on/off', None, 'toggle_grid', False), (None, None, None, None, None), ('Active', 'When enabled future plots will overwrite this figure', None, 'active', True), ('Hold', 'When enabled this holds this figure open ', None, 'hold', False), (None, None, None, None, None), ('Save', 'Save the figure', 'fa.save', 'save_figure', None), ('Print', 'Print the figure', 'fa.print', 'print_figure', None), (None, None, None, None, None), ('Customize', 'Configure plot options', 'fa.cog', 'edit_parameters', None)) def _init_toolbar(self): for text, tooltip_text, fa_icon, callback, checked in self.toolitems: if text is None: self.addSeparator() else: if fa_icon: a = self.addAction(qta.icon(fa_icon), text, getattr(self, callback)) else: a = self.addAction(text, getattr(self, callback)) self._actions[callback] = a if checked is not None: a.setCheckable(True) a.setChecked(checked) if tooltip_text is not None: a.setToolTip(tooltip_text) self.buttons = {} # Add the x,y location widget at the right side of the toolbar # The stretch factor is 1 which means any resizing of the toolbar # will resize this label instead of the buttons. if self.coordinates: self.locLabel = QtWidgets.QLabel("", self) self.locLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) self.locLabel.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.Expanding, QtWidgets.QSizePolicy.Ignored)) labelAction = self.addWidget(self.locLabel) labelAction.setVisible(True) # reference holder for subplots_adjust window self.adj_window = None # Adjust icon size or they are too small in PyQt5 by default self.setIconSize(QtCore.QSize(24, 24)) def toggle_grid(self): self.sig_grid_toggle_triggered.emit() def hold(self, *args): self._actions['hold'].setChecked(True) self._actions['active'].setChecked(False) self.sig_hold_triggered.emit() def active(self, *args): self._actions['active'].setChecked(True) self._actions['hold'].setChecked(False) self.sig_active_triggered.emit() def print_figure(self): printer = QtPrintSupport.QPrinter( QtPrintSupport.QPrinter.HighResolution) printer.setOrientation(QtPrintSupport.QPrinter.Landscape) print_dlg = QtPrintSupport.QPrintDialog(printer) if print_dlg.exec_() == QtWidgets.QDialog.Accepted: painter = QtGui.QPainter(printer) page_size = printer.pageRect() pixmap = self.canvas.grab().scaled(page_size.width(), page_size.height(), QtCore.Qt.KeepAspectRatio) painter.drawPixmap(0, 0, pixmap) painter.end()